diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/cells.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/cells.js deleted file mode 100644 index 83303f48a..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/cells.js +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import { Control } from 'react-redux-form' - -import './style.css' - -import { InputGroupButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem, - InputGroup } from 'reactstrap' -import classnames from 'classnames' - -import Icon from '../../../../Icon' - -export const GridCell = ({ cellData, rowIndex, colIndex, colName }) => - - -export const CellTypeSelector = ({ type, setType, - actions, disabled=false }) => { - - const [dropdownOpen, setDropdownOpen] = useState(false) - - return ( - setDropdownOpen(!dropdownOpen) } - > - - - - - Data type - setType('string') } - > - Text (categorical) - - setType('number') } - > - Numerical (continuous) - - setType('boolean') } - > - Boolean (binary) - - { - actions - ?
- - - Actions - - { - Object.entries(actions).map(([k, v], i) => - - { k } - - ) - } -
- :
- } -
-
- ) -} - -export const HeaderCell = ({ columnData, index }, { gridDispatch }) => - - - gridDispatch('change', { - model: `local.templateParameters.columns[${ index }]['type']`, - value - }) - } - actions={{ - 'Fill': () => { - gridDispatch('fillColumn', index) - }, - 'Clear': () => { - gridDispatch('clearColumn', index) - }, - 'Delete': () => { - if (window.confirm('Are you sure you want to delete this column?')) { - gridDispatch('deleteColumn', index) - } - } - }} - /> - - -HeaderCell.contextTypes = { - gridDispatch: PropTypes.func, -} diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/FactorialModal.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/FactorialModal.js deleted file mode 100644 index ad532d9a0..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/FactorialModal.js +++ /dev/null @@ -1,175 +0,0 @@ -import React, { Component } from 'react' -import { ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' -import { Control, LocalForm } from 'react-redux-form' - -import Modal from '../../../../../Modal' -import Grid from '../../../../../RRFGrid' -import { GridCell as InputCell } from '../cells' - -// Copied from the library combinatorics implementation -const product = function* (...sets) { - let thresholds = sets - .map(s => Math.max(s.length, 1)).reverse() - .reduce( - (acc, current, i) => acc.concat([ - (acc[i - 1] || 1) * current - ]), [] - ) - .reverse() - - for (let counter = 0; counter < thresholds[0]; counter++) { - yield sets.map( - (s, i) => s[Math.floor(counter / (thresholds[i + 1] || 1)) % s.length] - ) - } -} - -const headers = ['Factor', 'Levels'] -const HeaderCell = ({ index }) => -
- { headers[index] } -
- -const TextAreaCell = ({ cellData, rowIndex, colIndex, colName }) => { - const lines = (cellData || '').split('\n').length - - return ( - - ) -} - -const GridCell = (props) => { - if (props.colIndex === 0) { - return - } else { - return - } -} - -class FactorialModal extends Component { - constructor(props) { - super(props) - - this.state = { - open: false, - formData: { - factors: { - rows: [ - ['Factor A', 'Level A1\nLevel A2'], - ['Factor B', 'Level B1\nLevel B2'] - ], - }, - }, - } - this.promiseHandlers = {} - } - - // TODO: Think about collapsing the logic from this modal, - // and the FileHandler, into one. - toggle() { - this.setState((prevState) => { - // Reject promise - if (prevState.active && this.promiseHandlers.reject) { - this.promiseHandlers.reject('FileSelector hidden') - } - - // Switch state - return { open: !prevState.open } - }) - } - - async show() { - this.toggle() - return new Promise( - (resolve, reject) => { - this.promiseHandlers = { - resolve: result => { - this.setState({ open: false }) - return resolve(result) - }, - reject - } - } - ) - } - - generate() { - const factors = this.state.formData.factors.rows - .map(r => r[0]) - const levels = this.state.formData.factors.rows - .map(r => r[1].split('\n').filter(c => c !== '')) - - const output = { - columns: factors.map(f => ({ name: f, type: 'string' })), - rows: Array.from(product(...levels)), - } - - if (this.promiseHandlers.resolve) { - this.promiseHandlers.resolve(output) - } - - return output - } - - render() { - return ( - - this.setState({ formData }) } - getDispatch={ dispatch => this.formDispatch = dispatch } - > -
- this.toggle() } - > - Generate factorial design - - -

Please specify the factors in your design, as well as their levels. The resulting design is fully crossed, meaning that in the loop, every level of a factor is combined with every level of every other factor.

-
-
- this.formDispatch(action) } - className="border-top-0" - /> - - - - -
-
-
- ) - } -} - -export default FactorialModal diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Group.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Group.js deleted file mode 100644 index e087b2e93..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Group.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react' -import { DropTarget } from 'react-dnd' - -import classnames from 'classnames' - -import Item from './Item' - -const groupTarget = { - drop: (targetProps, monitor) => { - const { column } = monitor.getItem() - targetProps.move( - column, // id - targetProps.groupId // target - ) - }, -} - -function collect(connect, monitor) { - return { - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver() - } -} - -const Group = ({ columns=[], title, connectDropTarget, isOver, children }) => - connectDropTarget( - - - { title } - - - { - children || columns.map((c, i) => - - ) - } - - - - - ) - -export default DropTarget('item', groupTarget, collect)(Group) diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Item.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Item.js deleted file mode 100644 index b01b2bd47..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/Item.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { DragSource } from 'react-dnd' - -const itemSource = { - beginDrag: ({ column }) => { - return { column } - }, -} - -function collect(connect, monitor) { - return { - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging(), - } -} - -const Item = ({ name, connectDragSource, isDragging }) => - connectDragSource( -
- - { name } - -
- ) - -export default DragSource('item', itemSource, collect)(Item) diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/index.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/index.js deleted file mode 100644 index 2865c700e..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/components/ShuffleGroups/index.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react' - -import { Table } from 'reactstrap' - -import Group from './Group' -import Icon from '../../../../../../Icon' - -export default ({ groups, moveHandler, globalShuffle }) => { - const nextGroup = Math.max( - ...Object.keys(groups) - .map(Number) - .filter(x => !isNaN(x)), - 0 - ) + 1 - - const entries = Object.entries(groups) - - return ( - - - {/* Default group */} - 0 - ? undefined - : - (no columns yet) - - } - /> - {/* Actual shuffled groups */} - { - entries - .filter(([group]) => group !== 'undefined') - .map(([group, columns]) => - - ) - } - {/* Empty group */} - - - - Drag columns into rows - - - - to create independently shuffled groups - - - - -
- ) -} diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/footer.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/footer.js deleted file mode 100644 index 73b0e4b0b..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/footer.js +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState, createRef } from 'react' -import PropTypes from 'prop-types' - -import { ButtonDropdown, Button, - DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap' -import { range } from 'lodash' - -import Icon from '../../../../Icon' -import FactorialModal from './components/FactorialModal' - -import Uploader from '../../../../Uploader' -import { saveAs } from 'file-saver' -import { parse, unparse } from 'papaparse' - -const exportGrid = (data, columns) => { - const output = unparse({ - fields: columns.map(c => c.name), - data - }) - - return saveAs( - new Blob( - [ output ], - { type: 'text/csv;charset=utf-8' } - ), - 'loop.csv' - ) -} - -export const Footer = ({ columns, data }, { gridDispatch }) => { - const [dropdownOpen, setDropdownOpen] = useState(false) - const factorialModal = createRef() - - return ( - - - - - - setDropdownOpen(!dropdownOpen) } - > - - - - Generate - factorialModal.current.show().then(result => { - gridDispatch('overwrite', result) - }) } - > - Factorial design - - - Repeat - { - const n = window.prompt('How many times?') - if (n) { - gridDispatch('overwrite', { - columns, - rows: data.flatMap(r => range(n).map(() => r)), - }) - } - } } - > - Each row - - { - const n = window.prompt('How many times?') - if (n) { - gridDispatch('overwrite', { - columns, - rows: range(n).flatMap(() => data), - }) - } - } } - > - Entire grid - - - CSV / TSV - { - // TODO: Close the dropdown when the file selector - // is shown. This needs some more work to make sure - // that the uploader component isn't removed from the - // page in between - setDropdownOpen(false) - - const parseResult = parse( - content.trim(), - { header: true } - ) - - if ( - // No detected issues - parseResult.errors.length === 0 || - // Data is present, but no delimiter found - (parseResult.data && parseResult.errors.length === 1 && - parseResult.errors[0].code === 'UndetectableDelimiter') - ) { - gridDispatch('overwrite', { - columns: Object.keys(parseResult.data[0]) - .map(c => ({ name: c, type: 'string' })), - rows: parseResult.data.map(r => Object.values(r)) - }) - } else { - console.log( - 'CSV import found errors: ', - parseResult.errors - ) - alert( - 'Sorry, I couldn\'t parse that CSV file. ' + - 'There seems to be an error. ' + - 'Could you check it, please?' - ) - } - } - } - > -
- Import -
-
- exportGrid(data, columns) } - > - Export - -
-
- - - - - ) -} - -Footer.contextTypes = { - gridDispatch: PropTypes.func, -} diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/index.js b/packages/builder/src/components/ComponentOptions/components/Content/Loop/index.js index 25031e154..42a350f99 100644 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/index.js +++ b/packages/builder/src/components/ComponentOptions/components/Content/Loop/index.js @@ -1,190 +1,13 @@ -import React, { Component } from 'react' -import { Control, actions } from 'react-redux-form' -import { Col, CardBody, - FormGroup, Input, CustomInput, InputGroup, Label } from 'reactstrap' +import React from 'react' -import { uniqBy, groupBy } from 'lodash' - -import Hint from '../../../../Hint' -import Form from '../../RRFForm' import Card from '../../../../Card' -import Grid from '../../../../RRFGrid' -import { GridCell, HeaderCell } from './cells' -import { Footer } from './footer' -import ShuffleGroups from './components/ShuffleGroups' - -// TODO: Move to a general-purpose utility module -import { integerOrPlaceholder } from '../../Behavior/components/Timeline/util' - -const switchLabels = ({ - templateParameters={ rows: [] }, - sample={ n: undefined } -}, labels) => { - const samples = parseInt(sample.n) - const parameters = templateParameters.rows.length - - if (samples < parameters) { - return labels[0] - } else if (isNaN(samples) || samples === parameters) { - return labels[1] - } else { - return labels[2] - } -} - -const SampleWidget = ({ data }) => - - - - - - - - - - - - - - - -export default class extends Component { - constructor(props) { - super(props) - this.formDispatch = () => console.log('invalid dispatch') - } - - render() { - const { id, data } = this.props - const { columns=[], rows=[] } = data.templateParameters || {} - return ( - <> - -
this.formDispatch = dispatch } - > - this.formDispatch(action) } - /> - - - - -
- c.shuffleGroup).length > 1 } - collapsable={ true } - wrapContent={ false } - className="mt-4" - > - -

Parameter groups

-
- c.name !== '') - .map((c, i) => ({ ...c, id: i })), - 'shuffleGroup' - ) - } - moveHandler={ - (key, target) => - this.formDispatch( - actions.change( - `local.templateParameters.columns[${ key }]['shuffleGroup']`, - target - ) - ) - } - globalShuffle={ data.sample.mode !== 'sequential' } - /> -
- - ) - } -} +export default () => + <> + + + + + diff --git a/packages/builder/src/components/ComponentOptions/components/Content/Loop/style.css b/packages/builder/src/components/ComponentOptions/components/Content/Loop/style.css deleted file mode 100644 index e8057e24a..000000000 --- a/packages/builder/src/components/ComponentOptions/components/Content/Loop/style.css +++ /dev/null @@ -1,23 +0,0 @@ -/* Reduce padding in a grid dropdown-toggle button */ -table.grid th .input-group button.dropdown-toggle { - padding: 0.5rem 0.8rem; -} - -/* Reduce size of input group dropdown toggle, - and hide icon, if columns are very narrow. */ -table.grid.grid-slim th .input-group button.dropdown-toggle { - padding: 0.5rem; -} - -table.grid.grid-slim th .input-group button.dropdown-toggle i { - display: none; -} - -table.grid.grid-slim th .input-group button.dropdown-toggle::after { - margin-left: 0; -} - -/* Highlight active dropdown item */ -.dropdown-menu .dropdown-item.dropdown-item-active { - background-color: var(--light); -}