1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Add new custom form field for single value selection (#504)

* created BoxSelection component

* wip

* design changes

* integrate with form

* refactoring

* used inside appearance component

* WIP, added space between options

* adde new fields to BoxSelection used for chain selection

* fixed errors

* removed access type option from publish.json

* updated component for chain selection

* updated for compute type on publish dataset

* added component to dockerImageOpions

* removed Dotdotdot component from options

* remove space

* styling updates, fix React warning for terms checkbox

Co-authored-by: Norbi <katunanorbert@gmai.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
Norbi 2021-05-17 16:32:01 +03:00 committed by GitHub
parent 82acbba5ce
commit f0ed9f68cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 261 additions and 60 deletions

View File

@ -28,8 +28,8 @@
"label": "Docker Image", "label": "Docker Image",
"placeholder": "e.g. python3.7", "placeholder": "e.g. python3.7",
"help": "Please select an image to run your algorithm.", "help": "Please select an image to run your algorithm.",
"type": "select", "type": "boxSelection",
"options": ["node:latest", "python:latest", "custom image"], "options": [],
"required": true "required": true
}, },
{ {

View File

@ -34,7 +34,7 @@
"name": "access", "name": "access",
"label": "Access Type", "label": "Access Type",
"help": "Choose how you want your files to be accessible for the specified price.", "help": "Choose how you want your files to be accessible for the specified price.",
"type": "select", "type": "boxSelection",
"options": ["Download", "Compute"], "options": ["Download", "Compute"],
"required": true "required": true
}, },

View File

@ -4,6 +4,9 @@ import styles from './InputElement.module.css'
import { InputProps } from '.' import { InputProps } from '.'
import FilesInput from '../../molecules/FormFields/FilesInput' import FilesInput from '../../molecules/FormFields/FilesInput'
import Terms from '../../molecules/FormFields/Terms' import Terms from '../../molecules/FormFields/Terms'
import BoxSelection, {
BoxSelectionOption
} from '../../molecules/FormFields/BoxSelection'
import Datatoken from '../../molecules/FormFields/Datatoken' import Datatoken from '../../molecules/FormFields/Datatoken'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import AssetSelection, { import AssetSelection, {
@ -91,7 +94,7 @@ export default function InputElement({
id={slugify(option)} id={slugify(option)}
type={type} type={type}
name={name} name={name}
checked={props.defaultChecked} defaultChecked={props.defaultChecked}
{...props} {...props}
/> />
<label className={styles.radioLabel} htmlFor={slugify(option)}> <label className={styles.radioLabel} htmlFor={slugify(option)}>
@ -125,6 +128,15 @@ export default function InputElement({
return <Datatoken name={name} {...field} {...props} /> return <Datatoken name={name} {...field} {...props} />
case 'terms': case 'terms':
return <Terms name={name} options={options} {...field} {...props} /> return <Terms name={name} options={options} {...field} {...props} />
case 'boxSelection':
return (
<BoxSelection
name={name}
options={(options as unknown) as BoxSelectionOption[]}
{...field}
{...props}
/>
)
default: default:
return prefix || postfix ? ( return prefix || postfix ? (
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}> <div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>

View File

@ -0,0 +1,57 @@
.boxSelectionsWrapper {
display: flex;
justify-content: space-between;
gap: 0 calc(var(--spacer) / 4);
}
.boxSelectionsWrapper > div {
width: 100%;
}
.boxSelection {
display: block;
flex: 1 1 0px;
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
color: var(--color-secondary);
cursor: pointer;
}
.disabled {
pointer-events: none;
opacity: 0.5;
}
.title {
font-weight: var(--font-weight-bold);
text-align: center;
}
.boxSelectionsWrapper input[type='radio'] {
position: fixed;
opacity: 0;
pointer-events: none;
}
input[type='radio']:checked + label {
color: var(--font-color-text);
border-color: var(--color-secondary);
}
.boxSelection svg {
width: var(--font-size-h4);
height: var(--font-size-h4);
fill: currentColor;
margin-bottom: calc(var(--spacer) / 5);
}
.boxSelectionsWrapper label {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
color: var(--color-secondary);
font-weight: normal;
}

View File

@ -0,0 +1,70 @@
import React, { ChangeEvent } from 'react'
import classNames from 'classnames/bind'
import Loader from '../../atoms/Loader'
import styles from './BoxSelection.module.css'
const cx = classNames.bind(styles)
export interface BoxSelectionOption {
name: string
checked: boolean
title: JSX.Element | string
icon?: JSX.Element
text?: JSX.Element | string
}
export default function BoxSelection({
name,
options,
disabled,
handleChange,
...props
}: {
name: string
options: BoxSelectionOption[]
disabled?: boolean
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
}): JSX.Element {
const styleClassesWrapper = cx({
boxSelectionsWrapper: true,
[styles.disabled]: disabled
})
const styleClassesInput = cx({
input: true,
radio: true
})
return (
<div className={styleClassesWrapper}>
{!options ? (
<Loader />
) : (
options.map((value: BoxSelectionOption) => (
<div key={value.name}>
<input
id={value.name}
type="radio"
className={styleClassesInput}
defaultChecked={value.checked}
onChange={(event) => handleChange(event)}
{...props}
disabled={disabled}
value={value.name}
name={name}
/>
<label
className={`${styles.boxSelection} ${styles.label}`}
htmlFor={value.name}
title={value.name}
>
{value.icon}
<span className={styles.title}>{value.title}</span>
{value.text}
</label>
</div>
))
)}
</div>
)
}

View File

@ -30,3 +30,7 @@
color: var(--font-color-text); color: var(--font-color-text);
border-color: var(--color-secondary); border-color: var(--color-secondary);
} }
.appearances div[class*='boxSelectionsWrapper'] {
padding-bottom: calc(var(--spacer) / 8);
}

View File

@ -1,42 +1,44 @@
import React, { ReactElement } from 'react' import React, { ReactElement, ChangeEvent } from 'react'
import { DarkMode } from 'use-dark-mode' import { DarkMode } from 'use-dark-mode'
import Button from '../../atoms/Button'
import FormHelp from '../../atoms/Input/Help' import FormHelp from '../../atoms/Input/Help'
import Label from '../../atoms/Input/Label' import Label from '../../atoms/Input/Label'
import styles from './Appearance.module.css'
import { ReactComponent as Moon } from '../../../images/moon.svg' import { ReactComponent as Moon } from '../../../images/moon.svg'
import { ReactComponent as Sun } from '../../../images/sun.svg' import { ReactComponent as Sun } from '../../../images/sun.svg'
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
const buttons = ['Light', 'Dark'] import styles from './Appearance.module.css'
export default function Appearance({ export default function Appearance({
darkMode darkMode
}: { }: {
darkMode: DarkMode darkMode: DarkMode
}): ReactElement { }): ReactElement {
return ( const options: BoxSelectionOption[] = [
<li> {
<Label htmlFor="">Appearance</Label> name: 'Light',
<div className={styles.buttons}> checked: !darkMode.value,
{buttons.map((button) => { title: 'Light',
const isDark = button === 'Dark' icon: <Sun />
const selected = },
(isDark && darkMode.value) || (!isDark && !darkMode.value) {
name: 'Dark',
checked: darkMode.value,
title: 'Dark',
icon: <Moon />
}
]
function handleChange(event: ChangeEvent<HTMLInputElement>) {
event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
}
return ( return (
<Button <li className={styles.appearances}>
key={button} <Label htmlFor="">Appearance</Label>
className={`${styles.button} ${selected ? styles.selected : ''}`} <BoxSelection
size="small" options={options}
style="text" name="appearanceMode"
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())} handleChange={handleChange}
> />
{isDark ? <Moon /> : <Sun />}
{button}
</Button>
)
})}
</div>
<FormHelp>Defaults to your OS setting, select to override.</FormHelp> <FormHelp>Defaults to your OS setting, select to override.</FormHelp>
</li> </li>
) )

View File

@ -17,3 +17,14 @@
.selected { .selected {
composes: selected from './Appearance.module.css'; composes: selected from './Appearance.module.css';
} }
.chains div[class*='boxSelectionsWrapper'] {
display: grid;
gap: calc(var(--spacer) / 4);
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
padding-bottom: calc(var(--spacer) / 8);
}
.chains label[class*='boxSelection'] {
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
}

View File

@ -1,53 +1,58 @@
import { ConfigHelperConfig } from '@oceanprotocol/lib' import { ConfigHelperConfig } from '@oceanprotocol/lib'
import React, { ReactElement } from 'react' import React, { ReactElement, ChangeEvent } from 'react'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import { getOceanConfig } from '../../../utils/ocean' import { getOceanConfig } from '../../../utils/ocean'
import Button from '../../atoms/Button'
import FormHelp from '../../atoms/Input/Help' import FormHelp from '../../atoms/Input/Help'
import Label from '../../atoms/Input/Label' import Label from '../../atoms/Input/Label'
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
import styles from './Chain.module.css' import styles from './Chain.module.css'
export default function Chain(): ReactElement { export default function Chain(): ReactElement {
const { web3 } = useWeb3() const { web3 } = useWeb3()
const { config, connect } = useOcean() const { config, connect } = useOcean()
async function connectOcean(networkName: string) { async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
const config = getOceanConfig(networkName) const config = getOceanConfig(event.target.value)
await connect(config) await connect(config)
} }
const chains = [ function isNetworkSelected(oceanConfig: string) {
{ name: 'ETH', oceanConfig: 'mainnet', label: 'Mainnet' }, return (config as ConfigHelperConfig).network === oceanConfig
{ name: 'Polygon/Matic', oceanConfig: 'polygon', label: 'Mainnet' }, }
{ name: 'Moonbase Alpha', oceanConfig: 'moonbeamalpha', label: 'Testnet' }
const options: BoxSelectionOption[] = [
{
name: 'mainnet',
checked: isNetworkSelected('mainnet'),
title: 'ETH',
text: 'Mainnet'
},
{
name: 'polygon',
checked: isNetworkSelected('polygon'),
title: 'Polygon/Matic',
text: 'Mainnet'
},
{
name: 'moonbeamalpha',
checked: isNetworkSelected('moonbeamalpha'),
title: 'Moonbase Alpha',
text: 'Testnet'
}
] ]
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432 // TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
// there are more considerations for users with a wallet connected (wallet network vs. setting network). // there are more considerations for users with a wallet connected (wallet network vs. setting network).
// For now, only show the setting for non-wallet users. // For now, only show the setting for non-wallet users.
return !web3 ? ( return !web3 ? (
<li> <li className={styles.chains}>
<Label htmlFor="">Chain</Label> <Label htmlFor="">Chain</Label>
<div className={styles.buttons}> <BoxSelection
{chains.map((button) => { options={options}
const selected = name="chain"
(config as ConfigHelperConfig).network === button.oceanConfig handleChange={connectOcean}
/>
return (
<Button
key={button.name}
className={`${styles.button} ${selected ? styles.selected : ''}`}
size="small"
style="text"
onClick={() => connectOcean(button.oceanConfig)}
>
{button.name}
<span>{button.label}</span>
</Button>
)
})}
</div>
<FormHelp>Switch the data source for the interface.</FormHelp> <FormHelp>Switch the data source for the interface.</FormHelp>
</li> </li>
) : null ) : null

View File

@ -14,7 +14,6 @@ import Button from '../../atoms/Button'
import { FormContent, FormFieldProps } from '../../../@types/Form' import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData' import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish' import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
const query = graphql` const query = graphql`
@ -63,6 +62,24 @@ export default function FormPublish(): ReactElement {
initialValues.dockerImage initialValues.dockerImage
) )
const dockerImageOptions = [
{
name: 'node:latest',
title: 'node:latest',
checked: true
},
{
name: 'python:latest',
title: 'python:latest',
checked: false
},
{
name: 'custom image',
title: 'custom image',
checked: false
}
]
// reset form validation on every mount // reset form validation on every mount
useEffect(() => { useEffect(() => {
setErrors({}) setErrors({})
@ -135,6 +152,11 @@ export default function FormPublish(): ReactElement {
<Field <Field
key={field.name} key={field.name}
{...field} {...field}
options={
field.type === 'boxSelection'
? dockerImageOptions
: field.options
}
component={Input} component={Input}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(e, field) handleFieldChange(e, field)

View File

@ -7,6 +7,8 @@ import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetadataPublishFormDataset } from '../../../@types/MetaData' import { MetadataPublishFormDataset } from '../../../@types/MetaData'
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish' import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { ReactComponent as Download } from '../../../images/download.svg'
import { ReactComponent as Compute } from '../../../images/compute.svg'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
import styles from './FormPublish.module.css' import styles from './FormPublish.module.css'
@ -61,6 +63,19 @@ export default function FormPublish(): ReactElement {
// setSubmitting(false) // setSubmitting(false)
}, [setErrors, setTouched]) }, [setErrors, setTouched])
const accessTypeOptions = [
{
name: 'Download',
title: 'Download',
icon: <Download />
},
{
name: 'Compute',
title: 'Compute',
icon: <Compute />
}
]
// Manually handle change events instead of using `handleChange` from Formik. // Manually handle change events instead of using `handleChange` from Formik.
// Workaround for default `validateOnChange` not kicking in // Workaround for default `validateOnChange` not kicking in
function handleFieldChange( function handleFieldChange(
@ -94,6 +109,9 @@ export default function FormPublish(): ReactElement {
<Field <Field
key={field.name} key={field.name}
{...field} {...field}
options={
field.type === 'boxSelection' ? accessTypeOptions : field.options
}
component={Input} component={Input}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(e, field) handleFieldChange(e, field)