Browse Source

added PD.array/objectToOptions

David Sehnal 5 years ago
parent
commit
79f430efb3

+ 1 - 1
src/apps/state-docs/pd-to-md.ts

@@ -38,7 +38,7 @@ function paramInfo(param: PD.Any, offset: number): string {
     }
 }
 
-function oToS(options: (readonly [string, string])[]) {
+function oToS(options: readonly (readonly [string, string])[]) {
     return options.map(o => `'${o[0]}'`).join(', ');
 }
 

+ 3 - 9
src/apps/viewer/extensions/cellpack/model.ts

@@ -279,14 +279,8 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
     })
 }
 
-const Representations = {
-    'spacefill': 'Spacefill',
-    'gaussian-surface': 'Gaussian Surface',
-    'point': 'Point',
-    'ellipsoid': 'Ellipsoid'
-}
-type RepresentationName = keyof typeof Representations
-const RepresentationOptions = Object.keys(Representations).map(r => [r, Representations[r as RepresentationName]]) as [RepresentationName, string][]
+const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
+type RepresentationName = (typeof RepresentationOptions)[0][0]
 
 export const LoadCellPackModel = StateAction.build({
     display: { name: 'Load CellPack Model' },
@@ -298,7 +292,7 @@ export const LoadCellPackModel = StateAction.build({
             ['influenza_model1.json', 'influenza_model1'],
             ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
             ['curveTest', 'Curve Test'],
-        ]),
+        ] as const),
         baseUrl: PD.Text(DefaultCellPackBaseUrl),
         preset: PD.Group({
             traceOnly: PD.Boolean(false),

+ 2 - 2
src/mol-model/loci.ts

@@ -12,8 +12,8 @@ import { CentroidHelper } from '../mol-math/geometry/centroid-helper';
 import { Vec3 } from '../mol-math/linear-algebra';
 import { OrderedSet } from '../mol-data/int';
 import { Structure } from './structure/structure';
-import { stringToWords } from '../mol-util/string';
 import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
+import { ParamDefinition } from '../mol-util/param-definition';
 
 /** A Loci that includes every loci */
 export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
@@ -226,7 +226,7 @@ namespace Loci {
         }
     }
     export type Granularity = keyof typeof Granularity
-    export const GranularityOptions = Object.keys(Granularity).map(n => [n, stringToWords(n)]) as [Granularity, string][]
+    export const GranularityOptions = ParamDefinition.objectToOptions(Granularity);
 
     export function applyGranularity(loci: Loci, granularity: Granularity) {
         return Granularity[granularity](loci)

+ 1 - 4
src/mol-plugin-ui/structure/representation.tsx

@@ -178,10 +178,7 @@ export class StructureRepresentationControls extends CollapsableControls<Collaps
     }
 
     renderControls() {
-        const presets = Object.keys(P).map(name => {
-            return [name, camelCaseToWords(name)] as [string, string]
-        })
-
+        const presets = PD.objectToOptions(P, camelCaseToWords);
         return <div>
             <div className='msp-control-row'>
                 <div className='msp-select-row'>

+ 1 - 1
src/mol-plugin/state/transforms/model.ts

@@ -577,7 +577,7 @@ export const StructureComplexElementTypes = {
 } as const
 export type StructureComplexElementTypes = keyof typeof StructureComplexElementTypes
 
-const StructureComplexElementTypeTuples = Object.keys(StructureComplexElementTypes).map(t => [t, t] as any);
+const StructureComplexElementTypeTuples = PD.objectToOptions(StructureComplexElementTypes);
 
 type StructureComplexElement = typeof StructureComplexElement
 const StructureComplexElement = PluginStateTransform.BuiltIn({

+ 1 - 4
src/mol-repr/shape/loci/angle.ts

@@ -6,7 +6,6 @@
 
 import { Loci } from '../../../mol-model/loci';
 import { RuntimeContext } from '../../../mol-task';
-import { stringToWords } from '../../../mol-util/string';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
 import { Text } from '../../../mol-geo/geometry/text/text';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -72,15 +71,13 @@ const AngleVisuals = {
     'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }) }),
     'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils),
 }
-type AngleVisualName = keyof typeof AngleVisuals
-const AngleVisualOptions = Object.keys(AngleVisuals).map(name => [name, stringToWords(name)] as [AngleVisualName, string])
 
 export const AngleParams = {
     ...VectorsParams,
     ...ArcParams,
     ...SectorParams,
     ...TextParams,
-    visuals: PD.MultiSelect<AngleVisualName>(['vectors', 'sector', 'text'], AngleVisualOptions),
+    visuals: PD.MultiSelect(['vectors', 'sector', 'text'], PD.objectToOptions(AngleVisuals)),
 }
 export type AngleParams = typeof AngleParams
 export type AngleProps = PD.Values<AngleParams>

+ 1 - 4
src/mol-repr/shape/loci/dihedral.ts

@@ -6,7 +6,6 @@
 
 import { Loci } from '../../../mol-model/loci';
 import { RuntimeContext } from '../../../mol-task';
-import { stringToWords } from '../../../mol-util/string';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
 import { Text } from '../../../mol-geo/geometry/text/text';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -78,8 +77,6 @@ const DihedralVisuals = {
     'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }) }),
     'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils),
 }
-type DihedralVisualName = keyof typeof DihedralVisuals
-const DihedralVisualOptions = Object.keys(DihedralVisuals).map(name => [name, stringToWords(name)] as [DihedralVisualName, string])
 
 export const DihedralParams = {
     ...VectorsParams,
@@ -87,7 +84,7 @@ export const DihedralParams = {
     ...ArcParams,
     ...SectorParams,
     ...TextParams,
-    visuals: PD.MultiSelect<DihedralVisualName>(['extenders', 'sector', 'text'], DihedralVisualOptions),
+    visuals: PD.MultiSelect(['extenders', 'sector', 'text'], PD.objectToOptions(DihedralVisuals)),
 }
 export type DihedralParams = typeof DihedralParams
 export type DihedralProps = PD.Values<DihedralParams>

+ 1 - 4
src/mol-repr/shape/loci/distance.ts

@@ -6,7 +6,6 @@
 
 import { Loci } from '../../../mol-model/loci';
 import { RuntimeContext } from '../../../mol-task';
-import { stringToWords } from '../../../mol-util/string';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
 import { Text } from '../../../mol-geo/geometry/text/text';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -49,13 +48,11 @@ const DistanceVisuals = {
     'lines': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DistanceData, LineParams>) => ShapeRepresentation(getLinesShape, Lines.Utils),
     'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DistanceData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils),
 }
-type DistanceVisualName = keyof typeof DistanceVisuals
-const DistanceVisualOptions = Object.keys(DistanceVisuals).map(name => [name, stringToWords(name)] as [DistanceVisualName, string])
 
 export const DistanceParams = {
     ...LineParams,
     ...TextParams,
-    visuals: PD.MultiSelect<DistanceVisualName>(['lines', 'text'], DistanceVisualOptions),
+    visuals: PD.MultiSelect(['lines', 'text'], PD.objectToOptions(DistanceVisuals)),
 }
 export type DistanceParams = typeof DistanceParams
 export type DistanceProps = PD.Values<DistanceParams>

+ 2 - 4
src/mol-repr/shape/loci/label.ts

@@ -6,7 +6,6 @@
 
 import { Loci } from '../../../mol-model/loci';
 import { RuntimeContext } from '../../../mol-task';
-import { stringToWords } from '../../../mol-util/string';
 import { Text } from '../../../mol-geo/geometry/text/text';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { ColorNames } from '../../../mol-util/color/names';
@@ -33,13 +32,12 @@ type TextParams = typeof TextParams
 const LabelVisuals = {
     'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<LabelData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils),
 }
-type LabelVisualName = keyof typeof LabelVisuals
-const LabelVisualOptions = Object.keys(LabelVisuals).map(name => [name, stringToWords(name)] as [LabelVisualName, string])
 
 export const LabelParams = {
     ...TextParams,
-    visuals: PD.MultiSelect<LabelVisualName>(['text'], LabelVisualOptions),
+    visuals: PD.MultiSelect(['text'], PD.objectToOptions(LabelVisuals)),
 }
+
 export type LabelParams = typeof LabelParams
 export type LabelProps = PD.Values<LabelParams>
 

+ 1 - 4
src/mol-repr/shape/loci/orientation.ts

@@ -6,7 +6,6 @@
 
 import { Loci } from '../../../mol-model/loci';
 import { RuntimeContext } from '../../../mol-task';
-import { stringToWords } from '../../../mol-util/string';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { ColorNames } from '../../../mol-util/color/names';
 import { ShapeRepresentation } from '../representation';
@@ -53,13 +52,11 @@ const OrientationVisuals = {
     'box': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<OrientationData, BoxParams>) => ShapeRepresentation(getBoxShape, Mesh.Utils),
     'ellipsoid': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<OrientationData, EllipsoidParams>) => ShapeRepresentation(getEllipsoidShape, Mesh.Utils),
 }
-type OrientationVisualName = keyof typeof OrientationVisuals
-const OrientationVisualOptions = Object.keys(OrientationVisuals).map(name => [name, stringToWords(name)] as [OrientationVisualName, string])
 
 export const OrientationParams = {
     ...AxesParams,
     ...BoxParams,
-    visuals: PD.MultiSelect<OrientationVisualName>(['box'], OrientationVisualOptions),
+    visuals: PD.MultiSelect(['box'], PD.objectToOptions(OrientationVisuals)),
     color: PD.Color(ColorNames.orange),
     scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
 }

+ 1 - 3
src/mol-repr/structure/representation/ball-and-stick.ts

@@ -21,8 +21,6 @@ const BallAndStickVisuals = {
     'intra-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitLinkParams>) => UnitsRepresentation('Intra-unit link cylinder', ctx, getParams, IntraUnitLinkVisual),
     'inter-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitLinkParams>) => ComplexRepresentation('Inter-unit link cylinder', ctx, getParams, InterUnitLinkVisual),
 }
-type BallAndStickVisualName = keyof typeof BallAndStickVisuals
-const BallAndStickVisualOptions = Object.keys(BallAndStickVisuals).map(name => [name, name] as [BallAndStickVisualName, string])
 
 export const BallAndStickParams = {
     ...ElementSphereParams,
@@ -31,7 +29,7 @@ export const BallAndStickParams = {
     unitKinds: PD.MultiSelect<UnitKind>(['atomic'], UnitKindOptions),
     sizeFactor: PD.Numeric(0.15, { min: 0.01, max: 10, step: 0.01 }),
     sizeAspectRatio: PD.Numeric(2/3, { min: 0.01, max: 3, step: 0.01 }),
-    visuals: PD.MultiSelect<BallAndStickVisualName>(['element-sphere', 'intra-link', 'inter-link'], BallAndStickVisualOptions),
+    visuals: PD.MultiSelect(['element-sphere', 'intra-link', 'inter-link'], PD.objectToOptions(BallAndStickVisuals))
 }
 export type BallAndStickParams = typeof BallAndStickParams
 export function getBallAndStickParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/carbohydrate.ts

@@ -19,14 +19,12 @@ const CarbohydrateVisuals = {
     'carbohydrate-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CarbohydrateLinkParams>) => ComplexRepresentation('Carbohydrate link cylinder', ctx, getParams, CarbohydrateLinkVisual),
     'carbohydrate-terminal-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CarbohydrateTerminalLinkParams>) => ComplexRepresentation('Carbohydrate terminal link cylinder', ctx, getParams, CarbohydrateTerminalLinkVisual),
 }
-type CarbohydrateVisualName = keyof typeof CarbohydrateVisuals
-const CarbohydrateVisualOptions = Object.keys(CarbohydrateVisuals).map(name => [name, name] as [CarbohydrateVisualName, string])
 
 export const CarbohydrateParams = {
     ...CarbohydrateSymbolParams,
     ...CarbohydrateLinkParams,
     ...CarbohydrateTerminalLinkParams,
-    visuals: PD.MultiSelect<CarbohydrateVisualName>(['carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'], CarbohydrateVisualOptions),
+    visuals: PD.MultiSelect(['carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'], PD.objectToOptions(CarbohydrateVisuals)),
 }
 export type CarbohydrateParams = typeof CarbohydrateParams
 export function getCarbohydrateParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/cartoon.ts

@@ -23,8 +23,6 @@ const CartoonVisuals = {
     'nucleotide-ring': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, NucleotideRingParams>) => UnitsRepresentation('Nucleotide ring mesh', ctx, getParams, NucleotideRingVisual),
     'direction-wedge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerDirectionParams>) => UnitsRepresentation('Polymer direction wedge', ctx, getParams, PolymerDirectionVisual)
 }
-type CartoonVisualName = keyof typeof CartoonVisuals
-const CartoonVisualOptions = Object.keys(CartoonVisuals).map(name => [name, name] as [CartoonVisualName, string])
 
 export const CartoonParams = {
     ...PolymerTraceParams,
@@ -33,7 +31,7 @@ export const CartoonParams = {
     ...NucleotideRingParams,
     ...PolymerDirectionParams,
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    visuals: PD.MultiSelect<CartoonVisualName>(['polymer-trace', 'polymer-gap', 'nucleotide-block'], CartoonVisualOptions),
+    visuals: PD.MultiSelect(['polymer-trace', 'polymer-gap', 'nucleotide-block'], PD.objectToOptions(CartoonVisuals)),
 }
 export type CartoonParams = typeof CartoonParams
 export function getCartoonParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/ellipsoid.ts

@@ -20,8 +20,6 @@ const EllipsoidVisuals = {
     'intra-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitLinkParams>) => UnitsRepresentation('Intra-unit link cylinder', ctx, getParams, IntraUnitLinkVisual),
     'inter-link': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitLinkParams>) => ComplexRepresentation('Inter-unit link cylinder', ctx, getParams, InterUnitLinkVisual),
 }
-type EllipsoidVisualName = keyof typeof EllipsoidVisuals
-const EllipsoidVisualOptions = Object.keys(EllipsoidVisuals).map(name => [name, name] as [EllipsoidVisualName, string])
 
 export const EllipsoidParams = {
     ...EllipsoidMeshParams,
@@ -31,7 +29,7 @@ export const EllipsoidParams = {
     sizeFactor: PD.Numeric(1, { min: 0.01, max: 10, step: 0.01 }),
     sizeAspectRatio: PD.Numeric(0.1, { min: 0.01, max: 3, step: 0.01 }),
     linkCap: PD.Boolean(true),
-    visuals: PD.MultiSelect<EllipsoidVisualName>(['ellipsoid-mesh', 'intra-link', 'inter-link'], EllipsoidVisualOptions),
+    visuals: PD.MultiSelect(['ellipsoid-mesh', 'intra-link', 'inter-link'], PD.objectToOptions(EllipsoidVisuals)),
 }
 export type EllipsoidParams = typeof EllipsoidParams
 export function getEllipsoidParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/gaussian-surface.ts

@@ -19,13 +19,11 @@ const GaussianSurfaceVisuals = {
     'gaussian-surface-texture-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface texture-mesh', ctx, getParams, GaussianSurfaceTextureMeshVisual),
     'gaussian-surface-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianWireframeParams>) => UnitsRepresentation('Gaussian surface wireframe', ctx, getParams, GaussianWireframeVisual),
 }
-type GaussianSurfaceVisualName = keyof typeof GaussianSurfaceVisuals
-const GaussianSurfaceVisualOptions = Object.keys(GaussianSurfaceVisuals).map(name => [name, name] as [GaussianSurfaceVisualName, string])
 
 export const GaussianSurfaceParams = {
     ...GaussianSurfaceMeshParams,
     ...GaussianWireframeParams,
-    visuals: PD.MultiSelect<GaussianSurfaceVisualName>(['gaussian-surface-mesh'], GaussianSurfaceVisualOptions),
+    visuals: PD.MultiSelect(['gaussian-surface-mesh'], PD.objectToOptions(GaussianSurfaceVisuals)),
 }
 export type GaussianSurfaceParams = typeof GaussianSurfaceParams
 export function getGaussianSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/label.ts

@@ -14,12 +14,10 @@ import { LabelTextVisual, LabelTextParams } from '../visual/label-text';
 const LabelVisuals = {
     'label-text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, LabelTextParams>) => ComplexRepresentation('Label text', ctx, getParams, LabelTextVisual),
 }
-type LabelVisualName = keyof typeof LabelVisuals
-const LabelVisualOptions = Object.keys(LabelVisuals).map(name => [name, name] as [LabelVisualName, string])
 
 export const LabelParams = {
     ...LabelTextParams,
-    visuals: PD.MultiSelect<LabelVisualName>(['label-text'], LabelVisualOptions),
+    visuals: PD.MultiSelect(['label-text'], PD.objectToOptions(LabelVisuals)),
 }
 export type LabelParams = typeof LabelParams
 export function getLabelParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/molecular-surface.ts

@@ -17,13 +17,11 @@ const MolecularSurfaceVisuals = {
     'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface mesh', ctx, getParams, MolecularSurfaceMeshVisual),
     'molecular-surface-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceWireframeParams>) => UnitsRepresentation('Molecular surface wireframe', ctx, getParams, MolecularSurfaceWireframeVisual),
 }
-type MolecularSurfaceVisualName = keyof typeof MolecularSurfaceVisuals
-const MolecularSurfaceVisualOptions = Object.keys(MolecularSurfaceVisuals).map(name => [name, name] as [MolecularSurfaceVisualName, string])
 
 export const MolecularSurfaceParams = {
     ...MolecularSurfaceMeshParams,
     ...MolecularSurfaceWireframeParams,
-    visuals: PD.MultiSelect<MolecularSurfaceVisualName>(['molecular-surface-mesh'], MolecularSurfaceVisualOptions),
+    visuals: PD.MultiSelect(['molecular-surface-mesh'], PD.objectToOptions(MolecularSurfaceVisuals)),
 }
 export type MolecularSurfaceParams = typeof MolecularSurfaceParams
 export function getMolecularSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/orientation.ts

@@ -15,12 +15,10 @@ import { OrientationEllipsoidMeshParams, OrientationEllipsoidMeshVisual } from '
 const OrientationVisuals = {
     'orientation-ellipsoid-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, OrientationEllipsoidMeshParams>) => UnitsRepresentation('Orientation ellipsoid mesh', ctx, getParams, OrientationEllipsoidMeshVisual),
 }
-type OrientationVisualName = keyof typeof OrientationVisuals
-const OrientationVisualOptions = Object.keys(OrientationVisuals).map(name => [name, name] as [OrientationVisualName, string])
 
 export const OrientationParams = {
     ...OrientationEllipsoidMeshParams,
-    visuals: PD.MultiSelect<OrientationVisualName>(['orientation-ellipsoid-mesh'], OrientationVisualOptions),
+    visuals: PD.MultiSelect(['orientation-ellipsoid-mesh'], PD.objectToOptions(OrientationVisuals)),
 }
 export type OrientationParams = typeof OrientationParams
 export function getOrientationParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/structure/representation/putty.ts

@@ -17,14 +17,12 @@ const PuttyVisuals = {
     'polymer-tube': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTubeParams>) => UnitsRepresentation('Polymer tube mesh', ctx, getParams, PolymerTubeVisual),
     'polymer-gap': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerGapParams>) => UnitsRepresentation('Polymer gap cylinder', ctx, getParams, PolymerGapVisual),
 }
-type PuttyVisualName = keyof typeof PuttyVisuals
-const PuttyVisualOptions = Object.keys(PuttyVisuals).map(name => [name, name] as [PuttyVisualName, string])
 
 export const PuttyParams = {
     ...PolymerTubeParams,
     ...PolymerGapParams,
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    visuals: PD.MultiSelect<PuttyVisualName>(['polymer-tube', 'polymer-gap'], PuttyVisualOptions),
+    visuals: PD.MultiSelect(['polymer-tube', 'polymer-gap'], PD.objectToOptions(PuttyVisuals)),
 }
 export type PuttyParams = typeof PuttyParams
 export function getPuttyParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 1 - 3
src/mol-repr/volume/isosurface.ts

@@ -146,13 +146,11 @@ const IsosurfaceVisuals = {
     'solid': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceMeshParams>) => VolumeRepresentation('Isosurface mesh', ctx, getParams, IsosurfaceMeshVisual),
     'wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceWireframeParams>) => VolumeRepresentation('Isosurface wireframe', ctx, getParams, IsosurfaceWireframeVisual),
 }
-type IsosurfaceVisualName = keyof typeof IsosurfaceVisuals
-const IsosurfaceVisualOptions = Object.keys(IsosurfaceVisuals).map(name => [name, name] as [IsosurfaceVisualName, string])
 
 export const IsosurfaceParams = {
     ...IsosurfaceMeshParams,
     ...IsosurfaceWireframeParams,
-    visuals: PD.MultiSelect<IsosurfaceVisualName>(['solid'], IsosurfaceVisualOptions),
+    visuals: PD.MultiSelect(['solid'], PD.objectToOptions(IsosurfaceVisuals)),
 }
 export type IsosurfaceParams = typeof IsosurfaceParams
 export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) {

+ 40 - 7
src/mol-util/param-definition.ts

@@ -11,7 +11,7 @@ import { Vec2 as Vec2Data, Vec3 as Vec3Data } from '../mol-math/linear-algebra';
 import { deepClone } from './object';
 import { Script as ScriptData } from '../mol-script/script';
 import { Legend } from './legend';
-import { camelCaseToWords } from './string';
+import { stringToWords } from './string';
 
 export namespace ParamDefinition {
     export interface Info {
@@ -65,9 +65,9 @@ export namespace ParamDefinition {
     export interface Select<T extends string | number> extends Base<T> {
         type: 'select'
         /** array of (value, label) tuples */
-        options: (readonly [T, string])[]
+        options: readonly (readonly [T, string])[]
     }
-    export function Select<T extends string | number>(defaultValue: T, options: (readonly [T, string])[], info?: Info): Select<T> {
+    export function Select<T extends string | number>(defaultValue: T, options: readonly (readonly [T, string])[], info?: Info): Select<T> {
         return setInfo<Select<T>>({ type: 'select', defaultValue: checkDefaultKey(defaultValue, options), options }, info)
     }
 
@@ -83,9 +83,9 @@ export namespace ParamDefinition {
     export interface MultiSelect<E extends string, T = E[]> extends Base<T> {
         type: 'multi-select'
         /** array of (value, label) tuples */
-        options: [E, string][]
+        options: readonly (readonly [E, string])[]
     }
-    export function MultiSelect<E extends string, T = E[]>(defaultValue: T, options: [E, string][], info?: Info): MultiSelect<E, T> {
+    export function MultiSelect<E extends string, T = E[]>(defaultValue: T, options: readonly (readonly [E, string])[], info?: Info): MultiSelect<E, T> {
         // TODO: check if default value is a subset of options?
         return setInfo<MultiSelect<E, T>>({ type: 'multi-select', defaultValue, options }, info)
     }
@@ -203,7 +203,7 @@ export namespace ParamDefinition {
     export function MappedStatic<C extends Params>(defaultKey: keyof C, map: C, info?: Info & { options?: [keyof C, string][] }): Mapped<NamedParamUnion<C>> {
         const options: [string, string][] = info && info.options
             ? info.options as [string, string][]
-            : Object.keys(map).map(k => [k, camelCaseToWords(k)]) as [string, string][];
+            : Object.keys(map).map(k => [k, stringToWords(k)]) as [string, string][];
         const name = checkDefaultKey(defaultKey, options);
         return setInfo<Mapped<NamedParamUnion<C>>>({
             type: 'mapped',
@@ -363,7 +363,40 @@ export namespace ParamDefinition {
         return false;
     }
 
-    function checkDefaultKey<T>(k: T, options: (readonly [T, string])[]) {
+    /**
+     * Map an object to a list of [K, string][] to be used as options, stringToWords for key used by default (or identity of null).
+     *
+     * if options is { [string]: string } and mapping is not provided, use the Value.
+     */
+    export function objectToOptions<K extends string, V>(options: { [k in K]: V }, f?: null | ((k: K, v: V) => string)): [K, string][] {
+        const ret: [K, string][] = [];
+        for (const k of Object.keys(options) as K[]) {
+            if (!f) {
+                if (typeof options[k as K] === 'string') ret.push([k as K, options[k as K] as any]);
+                else ret.push([k as K, f === null ? k : stringToWords(k)]);
+            } else {
+                ret.push([k, f(k as K, options[k as K])])
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Map array of options using stringToWords by default (or identity of null).
+     */
+    export function arrayToOptions<V extends string>(xs: readonly V[], f?: null | ((v: V) => string)): [V, string][] {
+        const ret: [V, string][] = [];
+        for (const x of xs) {
+            if (!f) {
+                ret.push([x, f === null ? x : stringToWords(x)]);
+            } else {
+                ret.push([x, f(x)])
+            }
+        }
+        return ret;
+    }
+
+    function checkDefaultKey<T>(k: T, options: readonly (readonly [T, string])[]) {
         for (const o of options) {
             if (o[0] === k) return k;
         }

+ 6 - 1
src/mol-util/string.ts

@@ -38,12 +38,17 @@ export function splitSnakeCase(str: string) {
     return str.replace(/_/g, ' ')
 }
 
+
 export function snakeCaseToWords(str: string) {
     return capitalize(splitSnakeCase(str))
 }
 
+function splitSnakeplitCase(str: string) {
+    return str.replace(/[_-]/g, ' ')
+}
+
 export function stringToWords(str: string) {
-    return capitalize(splitCamelCase(splitSnakeCase(str)))
+    return capitalize(splitCamelCase(splitSnakeplitCase(str)))
 }
 
 export function substringStartsWith(str: string, start: number, end: number, target: string) {