UNPKG

molstar

Version:

A comprehensive macromolecular library.

224 lines (223 loc) 11.5 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import * as React from 'react'; import { Button, ControlGroup } from './common'; import { CloseSvg, ArrowDropDownSvg, ArrowRightSvg, CheckSvg } from './icons'; export class ActionMenu extends React.PureComponent { constructor() { super(...arguments); this.hide = () => this.props.onSelect(void 0); } render() { const cmd = this.props; const section = _jsx(Section, { items: cmd.items, onSelect: cmd.onSelect, current: cmd.current, multiselect: this.props.multiselect, noOffset: this.props.noOffset, noAccent: this.props.noAccent }); return _jsxs("div", { className: `msp-action-menu-options${cmd.header ? '' : ' msp-action-menu-options-no-header'}`, children: [cmd.header && _jsx(ControlGroup, { header: cmd.header, title: cmd.title, initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.hide, topRightIcon: CloseSvg, children: section }), !cmd.header && section] }); } } (function (ActionMenu) { function Header(label, options) { return options ? { kind: 'header', label, ...options } : { kind: 'header', label }; } ActionMenu.Header = Header; function Item(label, value, options) { return { kind: 'item', label, value, ...options }; } ActionMenu.Item = Item; function createItems(xs, params) { const { label, value, category, selected, icon, addOn, description } = params || {}; let cats = void 0; const items = []; for (let i = 0; i < xs.length; i++) { const x = xs[i]; if ((params === null || params === void 0 ? void 0 : params.filter) && !params.filter(x)) continue; const catName = category === null || category === void 0 ? void 0 : category(x); const l = label ? label(x) : '' + x; const v = value ? value(x) : x; const d = description ? description(x) : typeof x === 'string' ? x : undefined; let cat; if (!!catName) { if (!cats) cats = new Map(); cat = cats.get(catName); if (!cat) { cat = [ActionMenu.Header(catName, { description: catName })]; cats.set(catName, cat); items.push(cat); } } else { cat = items; } const ao = addOn === null || addOn === void 0 ? void 0 : addOn(x); cat.push({ kind: 'item', label: l, value: v, icon: icon ? icon(x) : void 0, selected: selected ? selected(x) : void 0, addOn: ao, description: d }); } return items; } ActionMenu.createItems = createItems; const _selectOptions = { value: (o) => o[0], label: (o) => o[1], category: (o) => o[2] }; function createItemsFromSelectOptions(options, params) { return createItems(options, params ? { ..._selectOptions, ...params } : _selectOptions); } ActionMenu.createItemsFromSelectOptions = createItemsFromSelectOptions; function hasSelectedItem(items) { if (isHeader(items)) return false; if (isItem(items)) return !!items.selected; for (const s of items) { const found = hasSelectedItem(s); if (found) return true; } return false; } ActionMenu.hasSelectedItem = hasSelectedItem; function findItem(items, value) { if (isHeader(items)) return; if (isItem(items)) return items.value === value ? items : void 0; for (const s of items) { const found = findItem(s, value); if (found) return found; } } ActionMenu.findItem = findItem; function getFirstItem(items) { if (isHeader(items)) return; if (isItem(items)) return items; for (const s of items) { const found = getFirstItem(s); if (found) return found; } } ActionMenu.getFirstItem = getFirstItem; // export type SelectProps<T> = { // items: Items, // onSelect: (item: Item) => void, // disabled?: boolean, // label?: string, // current?: Item, // style?: React.CSSProperties // } // export class Select<T> extends React.PureComponent<SelectProps<T>, { isExpanded: boolean }> { // state = { isExpanded: false }; // toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded }) // onSelect: OnSelect = (item) => { // this.setState({ isExpanded: false }); // if (!item) return; // this.onSelect(item); // } // render() { // const current = this.props.current; // const label = this.props.label || current?.label || ''; // return <div className='msp-action-menu-select' style={this.props.style}> // <ToggleButton disabled={this.props.disabled} style={{ textAlign: 'left' }} className='msp-no-overflow' // label={label} title={label as string} toggle={this.toggleExpanded} isSelected={this.state.isExpanded} /> // {this.state.isExpanded && <ActionMenu items={this.props.items} current={this.props.current} onSelect={this.onSelect} />} // </div> // } // } })(ActionMenu || (ActionMenu = {})); class Section extends React.PureComponent { constructor() { super(...arguments); this.state = Section.createState(this.props); this.toggleExpanded = (e) => { this.setState({ isExpanded: !this.state.isExpanded }); e.currentTarget.blur(); }; this.selectAll = () => { const items = collectItems(this.props.items, []).filter(i => !i.selected); this.props.onSelect(items); }; this.selectNone = () => { const items = collectItems(this.props.items, []).filter(i => !!i.selected); this.props.onSelect(items); }; } static createState(props, isExpanded) { const header = isItems(props.items) && isHeader(props.items[0]) ? props.items[0] : void 0; const hasCurrent = (header === null || header === void 0 ? void 0 : header.isIndependent) ? false : props.multiselect ? ActionMenu.hasSelectedItem(props.items) : (!!props.current && !!ActionMenu.findItem(props.items, props.current.value)) || ActionMenu.hasSelectedItem(props.items); return { header, hasCurrent, isExpanded: hasCurrent || (isExpanded !== null && isExpanded !== void 0 ? isExpanded : !!(header === null || header === void 0 ? void 0 : header.initiallyExpanded)) }; } componentDidUpdate(prevProps) { if (this.props.items !== prevProps.items || this.props.current !== prevProps.current) { // keep previously expanded section if the header label is the same const isExpanded = (isItems(this.props.items) && isItems(prevProps.items) && isHeader(this.props.items[0]) && isHeader(prevProps.items[0]) && this.props.items[0].label === prevProps.items[0].label) ? this.state.isExpanded : undefined; this.setState(Section.createState(this.props, isExpanded)); } } get multiselectHeader() { const { header, hasCurrent } = this.state; return _jsxs("div", { className: 'msp-flex-row msp-control-group-header', children: [_jsx(Button, { icon: this.state.isExpanded ? ArrowDropDownSvg : ArrowRightSvg, flex: true, noOverflow: true, onClick: this.toggleExpanded, title: `Click to ${this.state.isExpanded ? 'collapse' : 'expand'}.${(header === null || header === void 0 ? void 0 : header.description) ? ` ${header === null || header === void 0 ? void 0 : header.description}` : ''}`, children: hasCurrent ? _jsx("b", { children: header === null || header === void 0 ? void 0 : header.label }) : header === null || header === void 0 ? void 0 : header.label }), _jsx(Button, { icon: CheckSvg, flex: true, onClick: this.selectAll, style: { flex: '0 0 50px', textAlign: 'right' }, children: "All" }), _jsx(Button, { icon: CloseSvg, flex: true, onClick: this.selectNone, style: { flex: '0 0 50px', textAlign: 'right' }, children: "None" })] }); } get basicHeader() { const { header, hasCurrent } = this.state; return _jsx("div", { className: 'msp-control-group-header', style: { marginTop: '1px' }, children: _jsx(Button, { noOverflow: true, icon: this.state.isExpanded ? ArrowDropDownSvg : ArrowRightSvg, onClick: this.toggleExpanded, title: `Click to ${this.state.isExpanded ? 'collapse' : 'expand'}. ${(header === null || header === void 0 ? void 0 : header.description) ? header === null || header === void 0 ? void 0 : header.description : ''}`, children: hasCurrent ? _jsx("b", { children: header === null || header === void 0 ? void 0 : header.label }) : header === null || header === void 0 ? void 0 : header.label }) }); } render() { const { items, onSelect, current } = this.props; if (isHeader(items)) return null; if (isItem(items)) return _jsx(Action, { item: items, onSelect: onSelect, current: current, multiselect: this.props.multiselect }); const { header } = this.state; return _jsxs(_Fragment, { children: [header && (this.props.multiselect && this.state.isExpanded ? this.multiselectHeader : this.basicHeader), _jsx("div", { className: this.props.noOffset ? void 0 : this.props.noAccent ? 'msp-control-offset' : 'msp-accent-offset', children: (!header || this.state.isExpanded) && items.map((x, i) => { if (isHeader(x)) return null; if (isItem(x)) return _jsx(Action, { item: x, onSelect: onSelect, current: current, multiselect: this.props.multiselect }, i); return _jsx(Section, { items: x, onSelect: onSelect, current: current, multiselect: this.props.multiselect, noAccent: true }, i); }) })] }); } } const Action = ({ item, onSelect, current, multiselect }) => { const isCurrent = current === item; const style = item.addOn ? { position: 'relative' } : void 0; return _jsxs(Button, { icon: item.icon, noOverflow: true, className: 'msp-action-menu-button', onClick: e => onSelect(multiselect ? [item] : item, e), disabled: item.disabled, style: style, title: item.description, children: [isCurrent || item.selected ? _jsx("b", { children: item.label }) : item.label, item.addOn] }); }; function isItems(x) { return !!x && Array.isArray(x); } function isItem(x) { const v = x; return v && v.kind === 'item'; } function isHeader(x) { const v = x; return v && v.kind === 'header'; } function collectItems(items, target) { if (isHeader(items)) return target; if (isItem(items)) { target.push(items); return target; } for (const i of items) { collectItems(i, target); } return target; }