Skip to content

Commit

Permalink
feat(card-editor): line card support in card editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Armendariz authored and Joel Armendariz committed Oct 19, 2020
1 parent 18bce41 commit 983620c
Show file tree
Hide file tree
Showing 19 changed files with 1,960 additions and 687 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
"@storybook/addon-storyshots": "^5.3.17",
"@storybook/addons": "^5.3.17",
"@storybook/react": "^5.3.17",
"@svgr/rollup": "^5.4.0",
"@testing-library/dom": "^7.22.2",
"@testing-library/jest-dom": "^5.11.3",
"@testing-library/react": "^10.4.8",
Expand Down
2 changes: 1 addition & 1 deletion src/components/BarChartCard/barChartUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export const formatColors = (series, datasetNames) => {
const colors = { identifier: 'group', scale: {} };

// if color is an array, order doesn't matter so just map as many as possible
if (Array.isArray(series[0].color)) {
if (series[0] && Array.isArray(series[0].color)) {
series[0].color.forEach((color, index) => {
colors.scale[datasetNames[index]] = color;
});
Expand Down
109 changes: 41 additions & 68 deletions src/components/CardEditor/CardEditForm/CardEditForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,37 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Code16 } from '@carbon/icons-react';

import {
CARD_SIZES,
CARD_DIMENSIONS,
ALLOWED_CARD_SIZES_PER_TYPE,
} from '../../../constants/LayoutConstants';
import { CARD_DIMENSIONS } from '../../../constants/LayoutConstants';
import { settings } from '../../../constants/Settings';
import { Tabs, Tab, Button, TextArea, TextInput, Dropdown } from '../../../index';
import { Tabs, Tab, Button } from '../../../index';
import CardCodeEditor from '../../CardCodeEditor/CardCodeEditor';

import CardEditFormContent from './CardEditFormContent';
import CardEditFormSettings from './CardEditFormSettings';

const { iotPrefix } = settings;

const propTypes = {
/** card data value */
cardJson: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** card data errors */
// errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** Callback function when form data changes */
onChange: PropTypes.func.isRequired,
i18n: PropTypes.shape({
openEditorButton: PropTypes.string,
}),
/** if provided, returns an array of strings which are the dataItems to be allowed
* on each card
* getValidDataItems(card, selectedTimeRange)
*/
getValidDataItems: PropTypes.func,
/** an array of dataItem string names to be included on each card
* this prop will be ignored if getValidDataItems is defined
*/
dataItems: PropTypes.arrayOf(PropTypes.string),
};

const defaultProps = {
cardJson: {},
i18n: {
Expand All @@ -36,18 +54,8 @@ const defaultProps = {
barChartLayout_VERTICAL: 'Vertical',
// additional card type names can be provided using the convention of `cardType_TYPE`
},
};

const propTypes = {
/** card data value */
cardJson: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** card data errors */
// errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** Callback function when form data changes */
onChange: PropTypes.func.isRequired,
i18n: PropTypes.shape({
openEditorButton: PropTypes.string,
}),
getValidDataItems: null,
dataItems: [],
};

/**
Expand Down Expand Up @@ -96,59 +104,13 @@ export const handleSubmit = (val, setError, onChange, setShowEditor) => {
return false;
};

const CardEditForm = ({ cardJson, /* errors, */ onChange, /* onAddImage, */ i18n }) => {
const CardEditForm = ({ cardJson, onChange, i18n, dataItems, getValidDataItems }) => {
const mergedI18n = { ...defaultProps.i18n, ...i18n };
const [showEditor, setShowEditor] = useState(false);
const [modalData, setModalData] = useState();

const baseClassName = `${iotPrefix}--card-edit-form`;

const cardContentsTab = (
<>
<div className={`${baseClassName}--input`}>
<TextInput
id="title"
labelText="Card title"
light
onChange={evt => onChange({ ...cardJson, title: evt.target.value })}
value={cardJson.title}
/>
</div>
<div className={`${baseClassName}--input`}>
<TextArea
id="description"
labelText="Description (Optional)"
light
onChange={evt => onChange({ ...cardJson, description: evt.target.value })}
value={cardJson.description}
/>
</div>
<div className={`${baseClassName}--input`}>
<Dropdown
id="size"
label="Select a size"
direction="bottom"
itemToString={item => item.text}
items={(ALLOWED_CARD_SIZES_PER_TYPE[cardJson.type] ?? Object.keys(CARD_SIZES)).map(
size => {
return {
id: size,
text: getCardSizeText(size, mergedI18n),
};
}
)}
light
selectedItem={{ id: cardJson.size, text: getCardSizeText(cardJson.size, mergedI18n) }}
onChange={({ selectedItem }) => {
onChange({ ...cardJson, size: selectedItem.id });
}}
titleText="Size"
/>
</div>
</>
);
const cardSettingsTab = <>SETTINGS</>;

return (
<>
{showEditor ? (
Expand All @@ -169,11 +131,22 @@ const CardEditForm = ({ cardJson, /* errors, */ onChange, /* onAddImage, */ i18n
<div className={baseClassName}>
<Tabs>
<Tab label="Content">
{/* <div className={`${baseClassName}--content`}>{cardContentsTab}</div> */}
<CardEditFormContent cardJson={cardJson} onChange={onChange} i18n={i18n} />
<CardEditFormContent
cardJson={cardJson}
onChange={onChange}
i18n={mergedI18n}
dataItems={dataItems}
getValidDataItems={getValidDataItems}
/>
</Tab>
<Tab label="Settings">
<div className={`${baseClassName}--content`}>{cardSettingsTab}</div>
<CardEditFormSettings
cardJson={cardJson}
onChange={onChange}
i18n={mergedI18n}
dataItems={dataItems}
getValidDataItems={getValidDataItems}
/>
</Tab>
</Tabs>
<div className={`${baseClassName}--footer`}>
Expand Down
137 changes: 78 additions & 59 deletions src/components/CardEditor/CardEditForm/CardEditFormContent.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Code16 } from '@carbon/icons-react';

import {
CARD_TYPES,
Expand All @@ -9,13 +8,39 @@ import {
ALLOWED_CARD_SIZES_PER_TYPE,
} from '../../../constants/LayoutConstants';
import { settings } from '../../../constants/Settings';
import { Tabs, Tab, Button, TextArea, TextInput, Dropdown } from '../../../index';
import CardCodeEditor from '../../CardCodeEditor/CardCodeEditor';
import { TextArea, TextInput, Dropdown } from '../../../index';
import { timeRangeToJSON } from '../../DashboardEditor/editorUtils';

import DataSeriesFormItem from './CardEditFormItems/DataSeriesFormItem';

const { iotPrefix } = settings;

const propTypes = {
/** card data value */
cardJson: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** card data errors */
// errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** Callback function when form data changes */
onChange: PropTypes.func.isRequired,
i18n: PropTypes.shape({
openEditorButton: PropTypes.string,
}),
/** if provided, returns an array of strings which are the timeRanges to be allowed
* on each card
* getValidTimeRanges(card, selectedDataItems)
*/
getValidTimeRanges: PropTypes.func,
/** if provided, returns an array of strings which are the dataItems to be allowed
* on each card
* getValidDataItems(card, selectedTimeRange)
*/
getValidDataItems: PropTypes.func,
/** an array of dataItem string names to be included on each card
* this prop will be ignored if getValidDataItems is defined
*/
dataItems: PropTypes.arrayOf(PropTypes.string),
};

const defaultProps = {
cardJson: {},
i18n: {
Expand All @@ -35,20 +60,28 @@ const defaultProps = {
barChartType_STACKED: 'Stacked',
barChartLayout_HORIZONTAL: 'Horizontal',
barChartLayout_VERTICAL: 'Vertical',
// additional card type names can be provided using the convention of `cardType_TYPE`
cardTitle: 'Card title',
description: 'Description (Optional)',
size: 'Size',
selectASize: 'Select a size',
timeRange: 'Time range',
selectATimeRange: 'Select a time range',
},
getValidDataItems: null,
getValidTimeRanges: null,
dataItems: [],
};

const propTypes = {
/** card data value */
cardJson: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** card data errors */
// errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** Callback function when form data changes */
onChange: PropTypes.func.isRequired,
i18n: PropTypes.shape({
openEditorButton: PropTypes.string,
}),
export const defaultTimeRangeOptions = {
last24Hours: 'Last 24 hrs',
last7Days: 'Last 7 days',
lastMonth: 'Last month',
lastQuarter: 'Last quarter',
lastYear: 'Last year',
thisWeek: 'This week',
thisMonth: 'This month',
thisQuarter: 'This quarter',
thisYear: 'This year',
};

/**
Expand All @@ -63,41 +96,14 @@ export const getCardSizeText = (size, i18n) => {
return `${sizeName} ${sizeDimensions}`;
};

/**
* Checks for JSON form errors
* @param {Object} val card JSON text input
* @param {Function} setError
* @param {Function} onChange
* @param {Function} setShowEditor
*/
export const handleSubmit = (val, setError, onChange, setShowEditor) => {
try {
let error = false;
if (val === '') {
setError('JSON value must not be an empty string');
error = true;
} else {
const json = JSON.parse(val);
// Check for non-exception throwing cases (false, 1234, null)
if (json && typeof json === 'object') {
onChange(json);
setShowEditor(false);
}
setError(`${val.substring(0, 8)} is not valid JSON`);
error = true;
}
if (!error) {
setError(false);
return true;
}
} catch (e) {
setError(e.message);
return false;
}
return false;
};

const CardEditFormContent = ({ cardJson, onChange, i18n }) => {
const CardEditFormContent = ({
cardJson,
onChange,
i18n,
dataItems,
getValidDataItems,
getValidTimeRanges,
}) => {
const { title, description, size, type } = cardJson;
const mergedI18n = { ...defaultProps.i18n, ...i18n };

Expand All @@ -108,7 +114,7 @@ const CardEditFormContent = ({ cardJson, onChange, i18n }) => {
<div className={`${baseClassName}--input`}>
<TextInput
id="title"
labelText="Card title"
labelText={mergedI18n.cardTitle}
light
onChange={evt => onChange({ ...cardJson, title: evt.target.value })}
value={title}
Expand All @@ -117,7 +123,7 @@ const CardEditFormContent = ({ cardJson, onChange, i18n }) => {
<div className={`${baseClassName}--input`}>
<TextArea
id="description"
labelText="Description (Optional)"
labelText={mergedI18n.description}
light
onChange={evt => onChange({ ...cardJson, description: evt.target.value })}
value={description}
Expand All @@ -126,7 +132,7 @@ const CardEditFormContent = ({ cardJson, onChange, i18n }) => {
<div className={`${baseClassName}--input`}>
<Dropdown
id="size"
label="Select a size"
label={mergedI18n.selectASize}
direction="bottom"
itemToString={item => item.text}
items={(ALLOWED_CARD_SIZES_PER_TYPE[type] ?? Object.keys(CARD_SIZES)).map(cardSize => {
Expand All @@ -140,27 +146,40 @@ const CardEditFormContent = ({ cardJson, onChange, i18n }) => {
onChange={({ selectedItem }) => {
onChange({ ...cardJson, size: selectedItem.id });
}}
titleText="Size"
titleText={mergedI18n.size}
/>
</div>
{type === CARD_TYPES.TIMESERIES && (
<>
<div className={`${baseClassName}--input`}>
<Dropdown
id="timeRange"
label="Select a time range"
label={mergedI18n.selectATimeRange}
direction="bottom"
itemToString={item => item.text}
items={[]}
items={
getValidTimeRanges ||
Object.keys(defaultTimeRangeOptions).map(range => ({
id: range,
text: defaultTimeRangeOptions[range],
}))
}
light
// selectedItem={{}}
onChange={({ selectedItem }) => {
// onChange({ ...cardJson, size: selectedItem.id });
const range = timeRangeToJSON[selectedItem.id];
onChange({ ...cardJson, dataSource: { ...cardJson.dataSource, range } });
}}
titleText="Time range"
titleText={mergedI18n.timeRange}
/>
</div>
<DataSeriesFormItem cardJson={[cardJson]} dataItems={[]} onChange={onChange} />
<DataSeriesFormItem
cardJson={cardJson}
onChange={onChange}
dataItems={dataItems}
getValidDataItems={getValidDataItems}
i18n={mergedI18n}
/>
</>
)}
</>
Expand Down
Loading

0 comments on commit 983620c

Please sign in to comment.