refactor row-wrapper according to new data structure
This commit is contained in:
132
src/data-table/data-cell.jsx
Normal file
132
src/data-table/data-cell.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ApplyPreMask } from '../masking';
|
||||
import { addSeparators } from '../utilities';
|
||||
|
||||
function formatMeasurementValue (measurement, styling) {
|
||||
// TODO: measurement.name is a horrible propertyname, it's actually the column header
|
||||
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
|
||||
let formattedMeasurementValue = '';
|
||||
if (isColumnPercentageBased) {
|
||||
if (isNaN(measurement.value)) {
|
||||
formattedMeasurementValue = styling.symbolForNulls;
|
||||
} else {
|
||||
formattedMeasurementValue = ApplyPreMask('0,00%', measurement.value);
|
||||
}
|
||||
} else {
|
||||
let magnitudeDivider;
|
||||
switch (measurement.magnitude.toLowerCase()) {
|
||||
case 'k':
|
||||
magnitudeDivider = 1000;
|
||||
break;
|
||||
case 'm':
|
||||
magnitudeDivider = 1000000;
|
||||
break;
|
||||
default:
|
||||
magnitudeDivider = 1;
|
||||
}
|
||||
const formattingStringWithoutMagnitude = measurement.format.replace(/k|K|m|M/gi, '');
|
||||
if (isNaN(measurement.value)) {
|
||||
formattedMeasurementValue = styling.symbolForNulls;
|
||||
} else {
|
||||
let preFormatValue = measurement.value;
|
||||
if (isColumnPercentageBased) {
|
||||
preFormatValue *= 100;
|
||||
}
|
||||
switch (formattingStringWithoutMagnitude) {
|
||||
case '#.##0':
|
||||
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), '.', ',', 0);
|
||||
break;
|
||||
case '#,##0':
|
||||
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), ',', '.', 0);
|
||||
break;
|
||||
default:
|
||||
formattedMeasurementValue = ApplyPreMask(
|
||||
formattingStringWithoutMagnitude,
|
||||
(preFormatValue / magnitudeDivider)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return formattedMeasurementValue;
|
||||
}
|
||||
|
||||
function getSemaphoreColors (measurement, semaphoreColors) {
|
||||
if (measurement < semaphoreColors.status.critical) {
|
||||
return semaphoreColors.statusColors.critical;
|
||||
}
|
||||
if (measurement < semaphoreColors.status.medium) {
|
||||
return semaphoreColors.statusColors.medium;
|
||||
}
|
||||
return semaphoreColors.statusColors.normal;
|
||||
}
|
||||
|
||||
const DataCell = ({ data, general, measurement, styleBuilder, styling }) => {
|
||||
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
|
||||
let formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
if (styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '.';
|
||||
}
|
||||
|
||||
let cellStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
paddingRight: '4px',
|
||||
textAlign: 'right'
|
||||
|
||||
};
|
||||
const { semaphoreColors } = styling;
|
||||
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const shouldHaveSemaphoreColors = semaphoreColors.fieldsToApplyTo.applyToAll || semaphoreColors.fieldsToApplyTo.specificFields.indexOf(measurement.name) !== -1;
|
||||
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
|
||||
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
|
||||
cellStyle = {
|
||||
backgroundColor,
|
||||
color,
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
paddingLeft: '4px',
|
||||
textAlign: 'right'
|
||||
};
|
||||
}
|
||||
|
||||
let cellClass = 'grid-cells';
|
||||
const shouldUseSmallCells = isColumnPercentageBased && data.headers.measurements.length > 1;
|
||||
if (shouldUseSmallCells) {
|
||||
cellClass = 'grid-cells-small';
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
style={cellStyle}
|
||||
>
|
||||
{formattedMeasurementValue}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
DataCell.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
measurements: PropTypes.array.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({
|
||||
cellSuffix: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
measurement: PropTypes.shape({
|
||||
format: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.any
|
||||
}).isRequired,
|
||||
styleBuilder: PropTypes.shape({
|
||||
hasComments: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
symbolForNulls: PropTypes.any.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default DataCell;
|
||||
28
src/data-table/header-padding.jsx
Normal file
28
src/data-table/header-padding.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const HeaderPadding = ({ styleBuilder, styling }) => {
|
||||
if (styling.usePadding && !styleBuilder.hasCustomFileStyle()) {
|
||||
const paddingStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
marginLeft: '15px'
|
||||
};
|
||||
return (
|
||||
<span style={paddingStyle} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
HeaderPadding.propTypes = {
|
||||
styleBuilder: PropTypes.shape({
|
||||
hasCustomFileStyle: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
fontFamily: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default HeaderPadding;
|
||||
106
src/data-table/index.jsx
Normal file
106
src/data-table/index.jsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import StyleBuilder from '../style-builder';
|
||||
import DataCell from './data-cell.jsx';
|
||||
import HeaderPadding from './header-padding.jsx';
|
||||
import { injectSeparators } from '../utilities';
|
||||
|
||||
const DataTable = ({ data, general, styling }) => {
|
||||
const {
|
||||
headers: {
|
||||
dimension1,
|
||||
measurements
|
||||
},
|
||||
matrix
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="row-wrapper">
|
||||
<table>
|
||||
{dimension1.map((dimensionEntry, dimensionIndex) => {
|
||||
const rowHeaderText = dimensionEntry.displayValue || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(styling);
|
||||
if (styling.hasCustomFileStyle) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(dimensionIndex);
|
||||
styleBuilder.applyCustomStyle({
|
||||
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
|
||||
});
|
||||
}
|
||||
const rowStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
|
||||
return (
|
||||
<tr key={dimensionEntry.displayValue}>
|
||||
<td
|
||||
className="fdim-cells"
|
||||
style={rowStyle}
|
||||
>
|
||||
<HeaderPadding
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
{dimensionEntry.displayValue}
|
||||
</td>
|
||||
{injectSeparators(
|
||||
matrix[dimensionIndex],
|
||||
styling.useSeparatorColumns,
|
||||
{ atEvery: measurements.length }
|
||||
).map(measurementData => {
|
||||
if (measurementData.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: `${12 + styling.options.fontSizeAdjustment}px`
|
||||
};
|
||||
|
||||
return (
|
||||
<td
|
||||
className="empty"
|
||||
key={`${dimensionEntry.displayValue}-${measurementData.name}-separator`}
|
||||
style={separatorStyle}
|
||||
>
|
||||
*
|
||||
</td>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DataCell
|
||||
data={data}
|
||||
general={general}
|
||||
key={`${dimensionEntry.displayValue}-${measurementData.name}`}
|
||||
measurement={measurementData}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DataTable.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
dimension1: PropTypes.array.isRequired
|
||||
}).isRequired,
|
||||
matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
hasCustomFileStyle: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
@@ -1,222 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ApplyPreMask } from './masking';
|
||||
import { addSeparators } from './utilities';
|
||||
|
||||
class ElseDimensionMeasures extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vSeparatorCols,
|
||||
measure_count,
|
||||
sufixCells,
|
||||
vSymbolForNulls,
|
||||
vLetterSize,
|
||||
vColorMetric1,
|
||||
vColorMetric1Text,
|
||||
vColorMetric2,
|
||||
vColorMetric2Text,
|
||||
vColorMetric3,
|
||||
vColorMetric3Text,
|
||||
vAllSemaphores,
|
||||
ConceptMatrixPivot,
|
||||
ConceptsAffectedMatrix,
|
||||
vAllMetrics,
|
||||
MetricsAffectedMatrix,
|
||||
vCritic,
|
||||
vMMedium,
|
||||
vNumMeasures,
|
||||
vNumMeasures2,
|
||||
MeasuresFormat,
|
||||
rowNumber,
|
||||
columnText,
|
||||
styleBuilder
|
||||
} = this.props;
|
||||
|
||||
// modified in here
|
||||
let columnNumber,
|
||||
vMaskNum,
|
||||
vColorSemaphore,
|
||||
vColorSemaphoreText,
|
||||
vDivide;
|
||||
|
||||
const measurementCells = [];
|
||||
|
||||
var nMeasure7 = 0;
|
||||
var nMeasure72 = -1;
|
||||
var nMeasure72Semaphore = 0;
|
||||
|
||||
for (var nMeasures22 = 1; nMeasures22 <= vNumMeasures2; nMeasures22++) {
|
||||
nMeasure7++;
|
||||
nMeasure72++;
|
||||
if (columnText.substring(0, 1) === '%') {
|
||||
columnNumber = ApplyPreMask('0,00%', ConceptMatrixPivot[rowNumber][nMeasures22]);
|
||||
var vSpecialF = '0,00%';
|
||||
} else {
|
||||
switch (MeasuresFormat[nMeasure72].substr(MeasuresFormat[nMeasure72].length - 1)) {
|
||||
case 'k':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'K':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
default:
|
||||
vDivide = 1;
|
||||
break;
|
||||
}
|
||||
var vSpecialF = MeasuresFormat[nMeasure72].replace(/k|K|m|M/gi, '');
|
||||
if (!isNaN(ConceptMatrixPivot[rowNumber][nMeasures22])) {
|
||||
vMaskNum = ConceptMatrixPivot[rowNumber][nMeasures22];
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%') {
|
||||
vMaskNum = vMaskNum * 100;
|
||||
}
|
||||
|
||||
switch (vSpecialF) {
|
||||
case '#.##0':
|
||||
columnNumber = addSeparators((vMaskNum / vDivide), '.', ',', 0);
|
||||
break;
|
||||
case '#,##0':
|
||||
columnNumber = addSeparators((vMaskNum / vDivide), ',', '.', 0);
|
||||
break;
|
||||
default:
|
||||
columnNumber = ApplyPreMask(vSpecialF, (vMaskNum / vDivide));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
columnNumber = vSymbolForNulls;
|
||||
}
|
||||
}
|
||||
|
||||
if (vSeparatorCols && nMeasure7 === (measure_count + 1)) {
|
||||
const seperatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: (12 + vLetterSize) + 'px'
|
||||
};
|
||||
const seperatorElement = (
|
||||
<th key={`${nMeasures22}-header`} className="empty" style={seperatorStyle}>
|
||||
*
|
||||
</th>
|
||||
);
|
||||
measurementCells.push(seperatorElement);
|
||||
nMeasure7 = 1;
|
||||
}
|
||||
if (nMeasure72 === (measure_count - 1)) {
|
||||
nMeasure72 = -1;
|
||||
nMeasure72Semaphore = measure_count;
|
||||
} else {
|
||||
nMeasure72Semaphore = nMeasure72 + 1;
|
||||
}
|
||||
|
||||
// apply the semaphores where needed
|
||||
if (styleBuilder.hasComments()) {
|
||||
columnNumber = '.';
|
||||
}
|
||||
|
||||
let cellElement;
|
||||
if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasure72Semaphore) >= 0) && !isNaN(ConceptMatrixPivot[rowNumber][nMeasures22]) && !styleBuilder.hasComments()) {
|
||||
if (ConceptMatrixPivot[rowNumber][nMeasures22] < vCritic) {
|
||||
vColorSemaphore = vColorMetric1;
|
||||
vColorSemaphoreText = vColorMetric1Text;
|
||||
} else {
|
||||
if (ConceptMatrixPivot[rowNumber][nMeasures22] < vMMedium) {
|
||||
vColorSemaphore = vColorMetric2;
|
||||
vColorSemaphoreText = vColorMetric2Text;
|
||||
} else {
|
||||
vColorSemaphore = vColorMetric3;
|
||||
vColorSemaphoreText = vColorMetric3Text;
|
||||
}
|
||||
}
|
||||
|
||||
const cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
color: vColorSemaphoreText,
|
||||
backgroundColor: vColorSemaphore,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px'
|
||||
};
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells-small' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
} else {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
textAlign: 'right',
|
||||
paddingRight: '4px'
|
||||
};
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells-small' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
} else {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
measurementCells.push(cellElement);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{measurementCells}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ElseDimensionMeasures.propTypes = {
|
||||
vFontFamily: PropTypes.any,
|
||||
vSeparatorCols: PropTypes.any,
|
||||
measure_count: PropTypes.any,
|
||||
sufixCells: PropTypes.any,
|
||||
vSymbolForNulls: PropTypes.any,
|
||||
vLetterSize: PropTypes.any,
|
||||
vColorMetric1: PropTypes.any,
|
||||
vColorMetric1Text: PropTypes.any,
|
||||
vColorMetric2: PropTypes.any,
|
||||
vColorMetric2Text: PropTypes.any,
|
||||
vColorMetric3: PropTypes.any,
|
||||
vColorMetric3Text: PropTypes.any,
|
||||
vAllSemaphores: PropTypes.any,
|
||||
ConceptMatrixPivot: PropTypes.any,
|
||||
ConceptsAffectedMatrix: PropTypes.any,
|
||||
vAllMetrics: PropTypes.any,
|
||||
MetricsAffectedMatrix: PropTypes.any,
|
||||
vCritic: PropTypes.any,
|
||||
vMMedium: PropTypes.any,
|
||||
vNumMeasures: PropTypes.any,
|
||||
vNumMeasures2: PropTypes.any,
|
||||
MeasuresFormat: PropTypes.any,
|
||||
rowNumber: PropTypes.any,
|
||||
columnText: PropTypes.any,
|
||||
styleBuilder: PropTypes.any
|
||||
};
|
||||
|
||||
export default ElseDimensionMeasures;
|
||||
@@ -1,5 +1,5 @@
|
||||
import '@babel/polyfill';
|
||||
import paint from './paint';
|
||||
import paint from './paint.jsx';
|
||||
import definition from './definition';
|
||||
import './main.less';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { distinctArray } from './utilities';
|
||||
|
||||
// TODO: rename colors
|
||||
function initializeColors ({ layout }) {
|
||||
console.log(layout);
|
||||
return {
|
||||
vColLibBlue: layout.collibblue,
|
||||
vColLibBlueP: layout.collibbluep,
|
||||
@@ -142,7 +141,6 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
|
||||
});
|
||||
|
||||
if (hasSecondDimension) {
|
||||
// console.log(row[0]);
|
||||
const currentDim1Entry = row[0].qText;
|
||||
const isSameDimension1AsPrevious = currentDim1Entry === previousDim1Entry;
|
||||
if (isSameDimension1AsPrevious) {
|
||||
|
||||
@@ -1,72 +1,64 @@
|
||||
import $ from 'jquery';
|
||||
import { enableExcelExport } from './excel-export';
|
||||
import HeaderWrapper from './header-wrapper.jsx';
|
||||
import RowWrapper, { prepareProps } from './row-wrapper.jsx';
|
||||
import initializeStore from './store';
|
||||
import React from 'react';
|
||||
// import ReactDOM from 'react-dom';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import HeadersTable from './headers-table/index.jsx';
|
||||
import DataTable from './data-table/index.jsx';
|
||||
|
||||
export default async function paint ($element, layout, component) {
|
||||
const state = initializeStore({
|
||||
const state = await initializeStore({
|
||||
$element,
|
||||
layout,
|
||||
component
|
||||
component,
|
||||
layout
|
||||
});
|
||||
|
||||
const {
|
||||
ArrayGetSelectedCount,
|
||||
vNumDims,
|
||||
ConceptMatrixColElem,
|
||||
ConceptMatrixColElemTable,
|
||||
ConceptMatrixRowElem,
|
||||
vSeparatorCols
|
||||
ConceptMatrixRowElem
|
||||
} = state.properties;
|
||||
|
||||
const rowWrapperProps = await prepareProps({
|
||||
state: {
|
||||
layout,
|
||||
colors: state.colors,
|
||||
...state.properties
|
||||
}
|
||||
});
|
||||
const {
|
||||
data: { meta: { dimensionCount } },
|
||||
selection: { dimensionSelectionCounts },
|
||||
styling: { useSeparatorColumns }
|
||||
} = state;
|
||||
|
||||
const jsx = (
|
||||
<React.Fragment>
|
||||
<div className="kpi-table">
|
||||
<HeaderWrapper
|
||||
dimensionInfos={state.dimensionInfos}
|
||||
measureInfos={state.measureInfos}
|
||||
{...state.properties}
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
styling={state.styling}
|
||||
/>
|
||||
<RowWrapper
|
||||
colors={state.colors}
|
||||
layout={layout}
|
||||
{...state.properties}
|
||||
{...rowWrapperProps}
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</div>
|
||||
<div className="data-table">
|
||||
<HeaderWrapper
|
||||
dimensionInfos={state.dimensionInfos}
|
||||
measureInfos={state.measureInfos}
|
||||
{...state.properties}
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
styling={state.styling}
|
||||
/>
|
||||
<RowWrapper
|
||||
colors={state.colors}
|
||||
layout={layout}
|
||||
{...state.properties}
|
||||
{...rowWrapperProps}
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
// TODO: switch to render when jquery interaction stuff in renderData is gone
|
||||
// ReactDOM.render(jsx, $element[0]);
|
||||
|
||||
const html = renderToStaticMarkup(jsx);
|
||||
|
||||
$element.html(html);
|
||||
// ReactDOM.render(jsx, $element[0]);
|
||||
|
||||
RenderData();
|
||||
|
||||
@@ -124,8 +116,8 @@ export default async function paint ($element, layout, component) {
|
||||
// colgado el menú de confirm, por eso uso este sistema, que sí funciona.
|
||||
// it can cause issues like error messages and wrong selections if there are null values
|
||||
// and the check allow null values is active
|
||||
if (vNumDims > 1 && indextd > 0) {
|
||||
if (ArrayGetSelectedCount[1] > 0) {
|
||||
if (dimensionCount > 1 && indextd > 0) {
|
||||
if (dimensionSelectionCounts[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElemTable));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
@@ -136,7 +128,7 @@ export default async function paint ($element, layout, component) {
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
if (indextd > 0 && ArrayGetSelectedCount[0] > 0) {
|
||||
if (indextd > 0 && dimensionSelectionCounts[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
@@ -155,13 +147,13 @@ export default async function paint ($element, layout, component) {
|
||||
|
||||
let SelectCol = 0;
|
||||
|
||||
if (vNumDims > 1 && indextd > 0) {
|
||||
if (ArrayGetSelectedCount[1] > 0) {
|
||||
if (dimensionCount > 1 && indextd > 0) {
|
||||
if (dimensionSelectionCounts[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElem));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
if (vSeparatorCols) {
|
||||
if (useSeparatorColumns) {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd / 2) - 1)];
|
||||
} else {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd) - 1)];
|
||||
@@ -180,7 +172,7 @@ export default async function paint ($element, layout, component) {
|
||||
let SelectRow = 0;
|
||||
SelectRow = ConceptMatrixRowElem[(indextr)];
|
||||
|
||||
if (ArrayGetSelectedCount[0] > 0) {
|
||||
if (dimensionSelectionCounts[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
@@ -1,81 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import StyleBuilder from './style-builder';
|
||||
|
||||
class RowList extends React.PureComponent {
|
||||
generatePaddingTextElement (hasCustomFileStyle) {
|
||||
const { vPadding, vFontFamily } = this.props;
|
||||
if (vPadding && !hasCustomFileStyle) {
|
||||
const paddingStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
marginLeft: '15px'
|
||||
};
|
||||
return (
|
||||
<span style={paddingStyle} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
vLetterSize,
|
||||
vCustomFileBool,
|
||||
vFontFamily,
|
||||
tableData,
|
||||
MeasurementsComponent
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{tableData.map((row, rowNumber) => {
|
||||
const rowHeaderText = row[0] || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(this.props);
|
||||
if (vCustomFileBool) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(rowNumber);
|
||||
styleBuilder.applyCustomStyle({ fontSize: (14 + vLetterSize) + 'px' });
|
||||
}
|
||||
|
||||
const rowStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
const paddingTextElement = this.generatePaddingTextElement(styleBuilder.hasCustomFileStyle());
|
||||
const measurementsProps = {
|
||||
rowHeaderText,
|
||||
rowNumber,
|
||||
styleBuilder
|
||||
};
|
||||
return (
|
||||
<tr key={rowNumber}>
|
||||
<td
|
||||
className="fdim-cells"
|
||||
style={rowStyle}
|
||||
>
|
||||
{paddingTextElement}
|
||||
{rowHeaderText}
|
||||
</td>
|
||||
<MeasurementsComponent
|
||||
columnText={rowHeaderText}
|
||||
{...this.props}
|
||||
{...measurementsProps}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RowList.propTypes = {
|
||||
tableData: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default RowList;
|
||||
@@ -1,129 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import ElseDimensionMeasures from './else-dimension-measures.jsx';
|
||||
import RowList from './row-list.jsx';
|
||||
import SingleDimensionMeasures from './single-dimension-measures.jsx';
|
||||
|
||||
const RowWrapper = props => {
|
||||
const {
|
||||
ConceptMatrix,
|
||||
ConceptMatrixPivot,
|
||||
vNumDims
|
||||
} = props;
|
||||
let MeasurementsComponent,
|
||||
tableData;
|
||||
if (vNumDims === 1) {
|
||||
tableData = ConceptMatrix;
|
||||
MeasurementsComponent = SingleDimensionMeasures;
|
||||
} else {
|
||||
tableData = ConceptMatrixPivot.filter(array => array.length);
|
||||
MeasurementsComponent = ElseDimensionMeasures;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row-wrapper">
|
||||
<table>
|
||||
<RowList
|
||||
MeasurementsComponent={MeasurementsComponent}
|
||||
tableData={tableData}
|
||||
{...props}
|
||||
/>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RowWrapper.propTypes = {
|
||||
ConceptMatrix: PropTypes.array.isRequired,
|
||||
ConceptMatrixPivot: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default RowWrapper;
|
||||
|
||||
export async function prepareProps ({ state }) {
|
||||
const { colors, layout, vAllSemaphores, vDynamicColorBody, vDynamicColorBodyP } = state;
|
||||
const props = {
|
||||
colors,
|
||||
vCustomFileBool: layout.customfilebool,
|
||||
vCustomFile: layout.customfile,
|
||||
vPadding: layout.indentbool,
|
||||
vPaddingText: '',
|
||||
vGlobalComas: 0,
|
||||
vGlobalComas2: 0,
|
||||
vGlobalComment: 0,
|
||||
vGlobalCommentColor: '',
|
||||
vGlobalFontSize: 0,
|
||||
vComas: 0,
|
||||
vMedium: false,
|
||||
vFontSize: '',
|
||||
vColorText: layout.BodyTextColorSchema,
|
||||
vDivide: 1,
|
||||
vSymbolForNulls: layout.symbolfornulls,
|
||||
vDynamicColorBody: 'vColLib' + layout.ColorSchema,
|
||||
vDynamicColorBodyP: 'vColLib' + layout.ColorSchema + 'P',
|
||||
vAllMetrics: layout.allmetrics,
|
||||
MetricsAffectedMatrix: JSON.parse('[' + layout.metricssemaphore + ']'),
|
||||
vColorMetric1: layout.colorstatus1.color,
|
||||
vColorMetric2: layout.colorstatus2.color,
|
||||
vColorMetric3: layout.colorstatus3.color,
|
||||
vColorMetric1Text: layout.colorstatus1text.color,
|
||||
vColorMetric2Text: layout.colorstatus2text.color,
|
||||
vColorMetric3Text: layout.colorstatus3text.color,
|
||||
vColorSemaphore: '',
|
||||
vColorSemaphoreText: '',
|
||||
vCritic: layout.metricsstatus1,
|
||||
vMMedium: layout.metricsstatus2,
|
||||
CustomArray: new Array(),
|
||||
CustomArrayBasic: new Array(),
|
||||
vNumCustomHeaders: 0,
|
||||
vColumnText: '',
|
||||
vColumnNum: '',
|
||||
vMaskNum: 0,
|
||||
StyleTags: '',
|
||||
vColorSchema: colors[vDynamicColorBody],
|
||||
vColorSchemaP: colors[vDynamicColorBodyP],
|
||||
vAllSemaphores: layout.allsemaphores,
|
||||
ConceptsAffectedMatrix: new Array(10)
|
||||
};
|
||||
if (vAllSemaphores == false) {
|
||||
props.ConceptsAffectedMatrix[0] = layout.conceptsemaphore1;
|
||||
props.ConceptsAffectedMatrix[1] = layout.conceptsemaphore2;
|
||||
props.ConceptsAffectedMatrix[2] = layout.conceptsemaphore3;
|
||||
props.ConceptsAffectedMatrix[3] = layout.conceptsemaphore4;
|
||||
props.ConceptsAffectedMatrix[4] = layout.conceptsemaphore5;
|
||||
props.ConceptsAffectedMatrix[5] = layout.conceptsemaphore6;
|
||||
props.ConceptsAffectedMatrix[6] = layout.conceptsemaphore7;
|
||||
props.ConceptsAffectedMatrix[7] = layout.conceptsemaphore8;
|
||||
props.ConceptsAffectedMatrix[8] = layout.conceptsemaphore9;
|
||||
props.ConceptsAffectedMatrix[9] = layout.conceptsemaphore10;
|
||||
}
|
||||
|
||||
function ReadCustomSchema () {
|
||||
var Url = '/Extensions/qlik-smart-pivot/' + props.vCustomFile;
|
||||
return $.get(Url).then(function (response) {
|
||||
var allTextLines = response.split(/\r\n|\n/);
|
||||
var headers = allTextLines[0].split(';');
|
||||
props.vNumCustomHeaders = headers.length;
|
||||
|
||||
for (var i = 0; i < allTextLines.length; i++) {
|
||||
props.CustomArray[i] = new Array(headers.length);
|
||||
var data = allTextLines[i].split(';');
|
||||
|
||||
if (data.length == headers.length) {
|
||||
for (var j = 0; j < headers.length; j++) {
|
||||
props.CustomArrayBasic[i] = data[0];
|
||||
props.CustomArray[i][j] = data[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const hasCustomSchema = (props.vCustomFileBool && props.vCustomFile.length > 4);
|
||||
const schemaPromise = hasCustomSchema ? ReadCustomSchema() : Promise.resolve();
|
||||
await schemaPromise;
|
||||
|
||||
return props;
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ApplyPreMask } from './masking';
|
||||
import { addSeparators } from './utilities';
|
||||
|
||||
class SingleDimensionMeasures extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vSymbolForNulls,
|
||||
vColorMetric1,
|
||||
vColorMetric1Text,
|
||||
vColorMetric2,
|
||||
vColorMetric2Text,
|
||||
vColorMetric3,
|
||||
vColorMetric3Text,
|
||||
ConceptMatrix,
|
||||
vAllSemaphores,
|
||||
ConceptsAffectedMatrix,
|
||||
vAllMetrics,
|
||||
MetricsAffectedMatrix,
|
||||
vCritic,
|
||||
vMMedium,
|
||||
vNumMeasures,
|
||||
MeasuresFormat,
|
||||
rowNumber,
|
||||
columnText,
|
||||
styleBuilder
|
||||
} = this.props;
|
||||
|
||||
// modified in here
|
||||
let vColumnNum,
|
||||
vMaskNum,
|
||||
vColorSemaphore,
|
||||
vColorSemaphoreText,
|
||||
vDivide;
|
||||
|
||||
const measurementCells = [];
|
||||
|
||||
// TODO: map ConceptMatrix[rowNumber] into cells
|
||||
for (var nMeasures2 = 1; nMeasures2 <= vNumMeasures; nMeasures2++) {
|
||||
var vSpecialF = MeasuresFormat[nMeasures2 - 1].replace(/k|K|m|M/gi, '');
|
||||
if (columnText.substring(0, 1) == '%') {
|
||||
vColumnNum = ApplyPreMask('0,00%', ConceptMatrix[rowNumber][nMeasures2]);
|
||||
vSpecialF = '0,00%';
|
||||
} else {
|
||||
const magnitude = MeasuresFormat[nMeasures2 - 1].substr(MeasuresFormat[nMeasures2 - 1].length - 1);
|
||||
switch (magnitude.toLowerCase()) {
|
||||
case 'k':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
default:
|
||||
vDivide = 1;
|
||||
break;
|
||||
}
|
||||
if (!isNaN(ConceptMatrix[rowNumber][nMeasures2])) {
|
||||
vMaskNum = ConceptMatrix[rowNumber][nMeasures2];
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) == '%') {
|
||||
vMaskNum = vMaskNum * 100;
|
||||
}
|
||||
switch (vSpecialF) {
|
||||
case '#.##0':
|
||||
vColumnNum = addSeparators((vMaskNum / vDivide), '.', ',', 0);
|
||||
break;
|
||||
|
||||
case '#,##0':
|
||||
vColumnNum = addSeparators((vMaskNum / vDivide), ',', '.', 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
vColumnNum = ApplyPreMask(vSpecialF, (vMaskNum / vDivide));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
vColumnNum = vSymbolForNulls;
|
||||
}
|
||||
}
|
||||
if (styleBuilder.hasComments()) {
|
||||
vColumnNum = '.';
|
||||
}
|
||||
// apply the semaphore styles where needed
|
||||
let cellStyle;
|
||||
if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasures2) >= 0) && !isNaN(ConceptMatrix[rowNumber][nMeasures2]) && !styleBuilder.hasComments()) {
|
||||
if (ConceptMatrix[rowNumber][nMeasures2] < vCritic) {
|
||||
vColorSemaphore = vColorMetric1;
|
||||
vColorSemaphoreText = vColorMetric1Text;
|
||||
} else {
|
||||
if (ConceptMatrix[rowNumber][nMeasures2] < vMMedium) {
|
||||
vColorSemaphore = vColorMetric2;
|
||||
vColorSemaphoreText = vColorMetric2Text;
|
||||
} else {
|
||||
vColorSemaphore = vColorMetric3;
|
||||
vColorSemaphoreText = vColorMetric3Text;
|
||||
}
|
||||
}
|
||||
|
||||
cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
color: vColorSemaphoreText,
|
||||
backgroundColor: vColorSemaphore,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px'
|
||||
};
|
||||
} else {
|
||||
cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
}
|
||||
|
||||
const measurementCell = (
|
||||
<td key={nMeasures2} className="grid-cells' + sufixCells + '" style={cellStyle}>
|
||||
{vColumnNum}
|
||||
</td>
|
||||
);
|
||||
|
||||
measurementCells.push(measurementCell);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{measurementCells}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SingleDimensionMeasures.propTypes = {
|
||||
vFontFamily: PropTypes.any,
|
||||
vSymbolForNulls: PropTypes.any,
|
||||
vColorMetric1: PropTypes.any,
|
||||
vColorMetric1Text: PropTypes.any,
|
||||
vColorMetric2: PropTypes.any,
|
||||
vColorMetric2Text: PropTypes.any,
|
||||
vColorMetric3: PropTypes.any,
|
||||
vColorMetric3Text: PropTypes.any,
|
||||
ConceptMatrix: PropTypes.any,
|
||||
vAllSemaphores: PropTypes.any,
|
||||
ConceptsAffectedMatrix: PropTypes.any,
|
||||
vAllMetrics: PropTypes.any,
|
||||
MetricsAffectedMatrix: PropTypes.any,
|
||||
vCritic: PropTypes.any,
|
||||
vMMedium: PropTypes.any,
|
||||
vNumMeasures: PropTypes.any,
|
||||
MeasuresFormat: PropTypes.any,
|
||||
rowNumber: PropTypes.any,
|
||||
columnText: PropTypes.any,
|
||||
styleBuilder: PropTypes.any
|
||||
};
|
||||
|
||||
export default SingleDimensionMeasures;
|
||||
@@ -1,16 +1,11 @@
|
||||
function StyleBuilder (state) {
|
||||
function StyleBuilder (styling) {
|
||||
const {
|
||||
CustomArray,
|
||||
CustomArrayBasic,
|
||||
vNumCustomHeaders,
|
||||
vColorSchema,
|
||||
vColorText,
|
||||
vColorSchemaP,
|
||||
vLetterSize,
|
||||
colors
|
||||
} = state;
|
||||
colors,
|
||||
customCSV,
|
||||
options
|
||||
} = styling;
|
||||
let style = {
|
||||
fontSize: `${14 + vLetterSize}px`
|
||||
fontSize: `${14 + options.fontSizeAdjustment}px`
|
||||
};
|
||||
let hasComments = false;
|
||||
let commentColor;
|
||||
@@ -18,9 +13,9 @@ function StyleBuilder (state) {
|
||||
|
||||
function applyStandardAttributes (rowNumber) {
|
||||
const isEven = rowNumber % 2 === 0;
|
||||
style.backgroundColor = isEven ? vColorSchema : vColorSchemaP;
|
||||
style.color = vColorText;
|
||||
style.fontSize = `${14 + vLetterSize}px`;
|
||||
style.backgroundColor = isEven ? options.backgroundColor : options.backgroundColorOdd;
|
||||
style.color = options.color;
|
||||
style.fontSize = `${13 + options.fontSizeAdjustment}px`;
|
||||
}
|
||||
|
||||
function applyColor (color) {
|
||||
@@ -28,6 +23,7 @@ function StyleBuilder (state) {
|
||||
commentColor = color;
|
||||
}
|
||||
|
||||
/* eslint-disable sort-keys*/
|
||||
const properties = {
|
||||
'<comment>': () => { hasComments = true; },
|
||||
// text
|
||||
@@ -46,17 +42,18 @@ function StyleBuilder (state) {
|
||||
// font color TODO: this is a color just like the others, but it applies to text instead.. any way to make it less weird?
|
||||
'<white>': () => { style.color = 'white'; },
|
||||
// font size
|
||||
'<large>': () => { style.fontSize = `${15 + vLetterSize}px`; },
|
||||
'<medium>': () => { style.fontSize = `${14 + vLetterSize}px`; },
|
||||
'<small>': () => { style.fontSize = `${13 + vLetterSize}px`; },
|
||||
'<large>': () => { style.fontSize = `${15 + options.fontSizeAdjustment}px`; },
|
||||
'<medium>': () => { style.fontSize = `${14 + options.fontSizeAdjustment}px`; },
|
||||
'<small>': () => { style.fontSize = `${13 + options.fontSizeAdjustment}px`; },
|
||||
// text alignment
|
||||
'<center>': () => { style.textAlign = 'center'; }
|
||||
};
|
||||
/* eslint-enable sort-keys */
|
||||
|
||||
// TODO: need to improve this, it has way too many false positives
|
||||
function isCSSColor (property) {
|
||||
const isHexColor = property.substring(0, 1) == '#';
|
||||
const isRGBColor = property.substring(0, 3).toUpperCase() == 'RGB';
|
||||
const isHexColor = property.substring(0, 1) === '#';
|
||||
const isRGBColor = property.substring(0, 3).toUpperCase() === 'RGB';
|
||||
|
||||
return isHexColor || isRGBColor;
|
||||
}
|
||||
@@ -84,27 +81,27 @@ function StyleBuilder (state) {
|
||||
}
|
||||
|
||||
function parseCustomFileStyle (columnText) {
|
||||
hasCustomFileStyle = true;
|
||||
for (let csvAttribute = 1; csvAttribute < vNumCustomHeaders; csvAttribute += 1) {
|
||||
for (let csvAttribute = 1; csvAttribute < customCSV.count; csvAttribute += 1) {
|
||||
let customAttribute = '';
|
||||
if (CustomArrayBasic.indexOf(columnText) < 0) {
|
||||
if (customCSV.basic.indexOf(columnText) < 0) {
|
||||
customAttribute = 'none';
|
||||
} else {
|
||||
customAttribute = CustomArray[CustomArrayBasic.indexOf(columnText)][csvAttribute];
|
||||
hasCustomFileStyle = true;
|
||||
customAttribute = customCSV.full[customCSV.basic.indexOf(columnText)][csvAttribute];
|
||||
}
|
||||
applyProperty(customAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
applyCustomStyle,
|
||||
applyProperty,
|
||||
applyStandardAttributes,
|
||||
getCommentColor: () => commentColor,
|
||||
getStyle: () => style,
|
||||
hasComments: () => hasComments,
|
||||
hasCustomFileStyle: () => hasCustomFileStyle,
|
||||
hasFontSize: () => Boolean(style.fontSize),
|
||||
hasComments: () => hasComments,
|
||||
applyStandardAttributes,
|
||||
applyProperty,
|
||||
applyCustomStyle,
|
||||
parseCustomFileStyle
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ export function onlyUnique (value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export function distinctArray (array) {
|
||||
return array
|
||||
.map(entry => JSON.stringify(entry))
|
||||
.filter(onlyUnique)
|
||||
.map(entry => JSON.parse(entry));
|
||||
}
|
||||
|
||||
export function addSeparators (nStr, thousandsSep, decimalSep, numDecimals) {
|
||||
let x1;
|
||||
nStr = nStr.toFixed(numDecimals);
|
||||
@@ -21,3 +28,25 @@ export function Deferred () {
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
export function injectSeparators (array, shouldHaveSeparator, suppliedOptions) {
|
||||
const defaultOptions = {
|
||||
atEvery: 1,
|
||||
separator: { isSeparator: true }
|
||||
};
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
...suppliedOptions
|
||||
};
|
||||
|
||||
if (!shouldHaveSeparator) {
|
||||
return array;
|
||||
}
|
||||
return array.reduce((result, entry, index) => {
|
||||
result.push(entry);
|
||||
if (index < array.length - 1 && (index + 1) % options.atEvery === 0) {
|
||||
result.push(options.separator);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user