diff --git a/src/apps/state-docs/pd-to-md.ts b/src/apps/state-docs/pd-to-md.ts index a55d372a06c822da0839c01d8cc612cff4a898f9..f725105b4028af9d934d5361e1a20198ea15c1ca 100644 --- a/src/apps/state-docs/pd-to-md.ts +++ b/src/apps/state-docs/pd-to-md.ts @@ -39,7 +39,7 @@ function paramInfo(param: PD.Any, offset: number): string { } } -function oToS(options: readonly (readonly [string, string] | [string, string, string])[]) { +function oToS(options: readonly (readonly [string, string] | readonly [string, string, string])[]) { return options.map(o => `'${o[0]}'`).join(', '); } diff --git a/src/mol-plugin-ui/controls/action-menu.tsx b/src/mol-plugin-ui/controls/action-menu.tsx index 653bca86bdb781d896b1d4d2c32fbce2fa01b59b..016a4db71cf127f89be927c5dd5ec53885aa457f 100644 --- a/src/mol-plugin-ui/controls/action-menu.tsx +++ b/src/mol-plugin-ui/controls/action-menu.tsx @@ -41,23 +41,24 @@ export namespace ActionMenu { return { label, value: iconOrValue }; } - function createSpecFromSelectParamSimple(param: ParamDefinition.Select<any>) { - const items: Item[] = []; - for (const [v, l] of param.options) { - items.push(ActionMenu.Item(l, v)); - } - return items as Items; - } + export function createItems<T>(xs: ArrayLike<T>, options?: { label?: (t: T) => string, value?: (t: T) => any, category?: (t: T) => string | undefined }) { + const { label, value, category } = options || { }; + let cats: Map<string, (ActionMenu.Item | string)[]> | undefined = void 0; + const items: (ActionMenu.Item | (ActionMenu.Item | string)[] | string)[] = []; + for (let i = 0; i < xs.length; i++) { + const x = xs[i]; + + const catName = category?.(x); + const l = label ? label(x) : '' + x; + const v = value ? value(x) : x; - function createSpecFromSelectParamCategories(param: ParamDefinition.Select<any>) { - const cats = new Map<string, (Item | string)[]>(); - const items: (Item | (Item | string)[] | string)[] = []; - for (const [v, l, c] of param.options) { - if (!!c) { - let cat = cats.get(c); + if (!!catName) { + if (!cats) cats = new Map<string, (ActionMenu.Item | string)[]>(); + + let cat = cats.get(catName); if (!cat) { - cat = [c]; - cats.set(c, cat); + cat = [catName]; + cats.set(catName, cat); items.push(cat); } cat.push(ActionMenu.Item(l, v)); @@ -65,21 +66,21 @@ export namespace ActionMenu { items.push(ActionMenu.Item(l, v)); } } - return items as Items; + return items as ActionMenu.Items; } + + type Opt = ParamDefinition.Select<any>['options'][0]; + const _selectOptions = { value: (o: Opt) => o[0], label: (o: Opt) => o[1], category: (o: Opt) => o[2] }; - export function createSpecFromSelectParam(param: ParamDefinition.Select<any>) { - for (const o of param.options) { - if (!!o[2]) return createSpecFromSelectParamCategories(param); - } - return createSpecFromSelectParamSimple(param); + export function createItemsFromSelectParam(param: ParamDefinition.Select<any>) { + return createItems(param.options, _selectOptions); } - export function findCurrent(items: Items, value: any): Item | undefined { + export function findItem(items: Items, value: any): Item | undefined { if (typeof items === 'string') return; if (isItem(items)) return items.value === value ? items : void 0; for (const s of items) { - const found = findCurrent(s, value); + const found = findItem(s, value); if (found) return found; } } @@ -101,7 +102,7 @@ class Section extends React.PureComponent<SectionProps, SectionState> { state = { items: this.props.items, current: this.props.current, - isExpanded: !!this.props.current && !!ActionMenu.findCurrent(this.props.items, this.props.current.value) + isExpanded: !!this.props.current && !!ActionMenu.findItem(this.props.items, this.props.current.value) } toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => { @@ -111,7 +112,7 @@ class Section extends React.PureComponent<SectionProps, SectionState> { static getDerivedStateFromProps(props: SectionProps, state: SectionState) { if (props.items === state.items && props.current === state.current) return null; - return { items: props.items, current: props.current, isExpanded: props.current && !!ActionMenu.findCurrent(props.items, props.current.value) } + return { items: props.items, current: props.current, isExpanded: props.current && !!ActionMenu.findItem(props.items, props.current.value) } } render() { @@ -120,7 +121,7 @@ class Section extends React.PureComponent<SectionProps, SectionState> { if (typeof items === 'string') return null; if (isItem(items)) return <Action item={items} onSelect={onSelect} current={current} /> - const hasCurrent = header && current && !!ActionMenu.findCurrent(items, current.value) + const hasCurrent = header && current && !!ActionMenu.findItem(items, current.value) return <div> {header && <div className='msp-control-group-header' style={{ marginTop: '1px' }}> diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index adb877a1cee25b9768ea347e9f9246813282b4e8..ddb081b50dd7dc3449f0fc282d76f4cd980aafed 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -345,16 +345,17 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri toggle = () => this.setState({ showOptions: !this.state.showOptions }); - items = memoizeLatest((param: PD.Select<any>) => ActionMenu.createSpecFromSelectParam(param)); + items = memoizeLatest((param: PD.Select<any>) => ActionMenu.createItemsFromSelectParam(param)); renderControl() { const items = this.items(this.props.param); - const current = this.props.value !== undefined ? ActionMenu.findCurrent(items, this.props.value) : void 0; + const current = this.props.value !== undefined ? ActionMenu.findItem(items, this.props.value) : void 0; const label = current ? current.label : typeof this.props.value === 'undefined' - ? `${ActionMenu.getFirstItem(items)?.label || ''} [Default]` - : `[Invalid] ${this.props.value}` + ? `${ActionMenu.getFirstItem(items)?.label || ''} [Default]` + : `[Invalid] ${this.props.value}`; + return <ToggleButton disabled={this.props.isDisabled} style={{ textAlign: 'left', overflow: 'hidden', textOverflow: 'ellipsis' }} label={label} title={label as string} toggle={this.toggle} isSelected={this.state.showOptions} />; } @@ -363,7 +364,7 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri if (!this.state.showOptions) return null; const items = this.items(this.props.param); - const current = ActionMenu.findCurrent(items, this.props.value); + const current = ActionMenu.findItem(items, this.props.value); return <ActionMenu items={items} current={current} onSelect={this.onSelect} />; } diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx index 6560374f3193985587d6b89671b3e9d9578e1af1..f7e380a84963e0cfea31e5e74c3aed2c9bc0bedd 100644 --- a/src/mol-plugin-ui/structure/selection.tsx +++ b/src/mol-plugin-ui/structure/selection.tsx @@ -17,26 +17,10 @@ import { StructureElement } from '../../mol-model/structure'; import { ActionMenu } from '../controls/action-menu'; import { ToggleButton } from '../controls/common'; -function createDefaultQueries() { - const cats = new Map<string, (ActionMenu.Item | string)[]>(); - const items: (ActionMenu.Item | (ActionMenu.Item | string)[] | string)[] = []; - for (const q of StructureSelectionQueryList) { - if (!!q.category) { - let cat = cats.get(q.category); - if (!cat) { - cat = [q.category]; - cats.set(q.category, cat); - items.push(cat); - } - cat.push(ActionMenu.Item(q.label, q)); - } else { - items.push(ActionMenu.Item(q.label, q)); - } - } - return items as ActionMenu.Items; -} - -export const DefaultQueries = createDefaultQueries() +export const DefaultQueries = ActionMenu.createItems(StructureSelectionQueryList, { + label: q => q.label, + category: q => q.category +}); const StructureSelectionParams = { granularity: Interactivity.Params.granularity, diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 064528cf1dcaed4fa8b74f2933ab496587071fe8..c835570ab397c7db04c08804bed2277b79b6d756 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -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 (readonly [T, string] | [T, string, string])[] + options: readonly (readonly [T, string] | readonly [T, string, string])[] } - export function Select<T extends string | number>(defaultValue: T, options: readonly (readonly [T, string] | [T, string, string])[], info?: Info): Select<T> { + export function Select<T extends string | number>(defaultValue: T, options: readonly (readonly [T, string] | readonly [T, string, string])[], info?: Info): Select<T> { return setInfo<Select<T>>({ type: 'select', defaultValue: checkDefaultKey(defaultValue, options), options }, info) } @@ -406,7 +406,7 @@ export namespace ParamDefinition { return ret; } - function checkDefaultKey<T>(k: T, options: readonly (readonly [T, string] | [T, string, string])[]) { + function checkDefaultKey<T>(k: T, options: readonly (readonly [T, string] | readonly [T, string, string])[]) { for (const o of options) { if (o[0] === k) return k; }