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:
parent
82acbba5ce
commit
f0ed9f68cb
@ -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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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}`}>
|
||||||
|
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal file
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal 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;
|
||||||
|
}
|
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal file
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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 />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
<Button
|
event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
|
||||||
key={button}
|
}
|
||||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
|
||||||
size="small"
|
return (
|
||||||
style="text"
|
<li className={styles.appearances}>
|
||||||
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())}
|
<Label htmlFor="">Appearance</Label>
|
||||||
>
|
<BoxSelection
|
||||||
{isDark ? <Moon /> : <Sun />}
|
options={options}
|
||||||
{button}
|
name="appearanceMode"
|
||||||
</Button>
|
handleChange={handleChange}
|
||||||
)
|
/>
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<FormHelp>Defaults to your OS setting, select to override.</FormHelp>
|
<FormHelp>Defaults to your OS setting, select to override.</FormHelp>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user