/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal * @author Alexander Rose */ import * as React from 'react' import { ParamDefinition as PD } from 'mol-util/param-definition'; import { camelCaseToWords } from 'mol-util/string'; import { ColorNames } from 'mol-util/color/tables'; import { Color } from 'mol-util/color'; import { Slider, Slider2 } from './slider'; export interface ParameterControlsProps

{ params: P, values: any, onChange: ParamOnChange, isDisabled?: boolean, onEnter?: () => void } export class ParameterControls

extends React.PureComponent, {}> { render() { const params = this.props.params; const values = this.props.values; return

{Object.keys(params).map(key => { const param = params[key]; if (param.isHidden) return null; const Control = controlFor(param); if (!Control) return null; return })}
; } } function controlFor(param: PD.Any): ParamControl | undefined { switch (param.type) { case 'value': return void 0; case 'boolean': return BoolControl; case 'number': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined' ? NumberRangeControl : NumberInputControl; case 'converted': return ConvertedControl; case 'multi-select': return MultiSelectControl; case 'color': return ColorControl; case 'vec3': return Vec3Control; case 'file': return FileControl; case 'select': return SelectControl; case 'text': return TextControl; case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined' ? BoundedIntervalControl : IntervalControl; case 'group': return GroupControl; case 'mapped': return MappedControl; case 'line-graph': return void 0; } console.warn(`${(param as any).type} has no associated UI component.`); return void 0; } // type ParamWrapperProps = { name: string, value: any, param: PD.Base, onChange: ParamOnChange, control: ValueControl, onEnter?: () => void, isEnabled?: boolean } export type ParamOnChange = (params: { param: PD.Base, name: string, value: any }) => void export interface ParamProps

= PD.Base> { name: string, value: P['defaultValue'], param: P, isDisabled?: boolean, onChange: ParamOnChange, onEnter?: () => void } export type ParamControl = React.ComponentClass> export abstract class SimpleParam

extends React.PureComponent> { protected update(value: any) { this.props.onChange({ param: this.props.param, name: this.props.name, value }); } abstract renderControl(): JSX.Element; render() { const label = this.props.param.label || camelCaseToWords(this.props.name); return

{label}
{this.renderControl()}
; } } export class BoolControl extends SimpleParam { onClick = (e: React.MouseEvent) => { this.update(!this.props.value); e.currentTarget.blur(); } renderControl() { return ; } } export class NumberInputControl extends SimpleParam { onChange = (e: React.ChangeEvent) => { this.update(+e.target.value); } renderControl() { return number input TODO } } export class NumberRangeControl extends SimpleParam { onChange = (v: number) => { this.update(v); } renderControl() { return } } export class TextControl extends SimpleParam { onChange = (e: React.ChangeEvent) => { const value = e.target.value; if (value !== this.props.value) { this.update(value); } } onKeyPress = (e: React.KeyboardEvent) => { if (!this.props.onEnter) return; if ((e.keyCode === 13 || e.charCode === 13)) { this.props.onEnter(); } } renderControl() { const placeholder = this.props.param.label || camelCaseToWords(this.props.name); return ; } } export class SelectControl extends SimpleParam> { onChange = (e: React.ChangeEvent) => { this.update(e.target.value); } renderControl() { return ; } } export class IntervalControl extends SimpleParam { onChange = (v: [number, number]) => { this.update(v); } renderControl() { return interval TODO; } } export class BoundedIntervalControl extends SimpleParam { onChange = (v: [number, number]) => { this.update(v); } renderControl() { return ; } } export class ColorControl extends SimpleParam { onChange = (e: React.ChangeEvent) => { this.update(Color(parseInt(e.target.value))); } renderControl() { return ; } } export class Vec3Control extends SimpleParam { // onChange = (e: React.ChangeEvent) => { // this.setState({ value: e.target.value }); // this.props.onChange(e.target.value); // } renderControl() { return vec3 TODO; } } export class FileControl extends React.PureComponent> { change(value: File) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } onChangeFile = (e: React.ChangeEvent) => { this.change(e.target.files![0]); } render() { const value = this.props.value; // return return
{value ? value.name : 'Select a file...'}
} } export class MultiSelectControl extends React.PureComponent>, { isExpanded: boolean }> { state = { isExpanded: false } change(value: PD.MultiSelect['defaultValue'] ) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } toggle(key: string) { return (e: React.MouseEvent) => { if (this.props.value.indexOf(key) < 0) this.change(this.props.value.concat(key)); else this.change(this.props.value.filter(v => v !== key)); e.currentTarget.blur(); } } toggleExpanded = (e: React.MouseEvent) => { this.setState({ isExpanded: !this.state.isExpanded }); e.currentTarget.blur(); } render() { const current = this.props.value; const label = this.props.param.label || camelCaseToWords(this.props.name); return <>
{label}
{this.props.param.options.map(([value, label]) =>
)}
; } } export class GroupControl extends React.PureComponent>, { isExpanded: boolean }> { state = { isExpanded: !!this.props.param.isExpanded } change(value: any ) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } onChangeParam: ParamOnChange = e => { this.change({ ...this.props.value, [e.name]: e.value }); } toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded }); render() { const params = this.props.param.params; const label = this.props.param.label || camelCaseToWords(this.props.name); return
{this.state.isExpanded &&
}
} } export class MappedControl extends React.PureComponent>> { change(value: PD.Mapped['defaultValue'] ) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } onChangeName: ParamOnChange = e => { // TODO: Cache values when changing types? this.change({ name: e.value, params: this.props.param.map(e.value).defaultValue }); } onChangeParam: ParamOnChange = e => { this.change({ name: this.props.value.name, params: e.value }); } render() { const value: PD.Mapped['defaultValue'] = this.props.value; const param = this.props.param.map(value.name); const label = this.props.param.label || camelCaseToWords(this.props.name); const Mapped = controlFor(param); const select = if (!Mapped) { return select; } return
{select}
} } export class ConvertedControl extends React.PureComponent>> { onChange: ParamOnChange = e => { this.props.onChange({ name: this.props.name, param: this.props.param, value: this.props.param.toValue(e.value) }); } render() { const value = this.props.param.fromValue(this.props.value); const Converted = controlFor(this.props.param.converted); if (!Converted) return null; return } }