UNPKG

audio-source-composer

Version:

Audio Source Composer

333 lines (282 loc) 10.8 kB
import React from "react"; import PropTypes from "prop-types"; import ASUIContextMenuContext from "../context/ASUIContextMenuContext"; import ASUIClickable from "../../clickable/ASUIClickable"; import ASUIMenuDropDown from "../dropdown/ASUIMenuDropDown"; import ASUIMenuItem from "../item/ASUIMenuItem"; import MenuOptionProcessor from "./MenuOptionProcessor"; import "./ASUIMenuOptionList.css"; import ASUIMenuBreak from "../break/ASUIMenuBreak"; import ASUIMenuAction from "../action/ASUIMenuAction"; export default class ASUIMenuOptionListBase extends React.Component { /** Menu Context **/ static contextType = ASUIContextMenuContext; /** @return {ASUIContextMenuContainer} **/ getOverlay() { return this.context.overlay; } /** @return {ASUIMenuOptionList} **/ getParentMenu() { return this.props.parentMenu || this.context.parentMenu; } // Default Props static defaultProps = { vertical: false, }; // Props Validation static propTypes = { options: PropTypes.any.isRequired, onClose: PropTypes.func.isRequired, vertical: PropTypes.bool, disabled: PropTypes.bool, }; constructor(props) { super(props); this.state = { offset: 0, limit: 25, options: null, positionSelected: this.props.positionSelected || null } this.ref = { container: React.createRef(), options: [] } this.cb = { onKeyDown: e => this.onKeyDown(e), } // console.log(`${this.constructor.name}.constructor`, props); } componentDidMount() { // console.log(`${this.constructor.name}.componentDidMount`); // this.refresh(); this.processOptions(); } componentDidUpdate(prevProps, prevState, snapshot) { // console.log('ASUIMenuOptionList.componentDidUpdate', this.state, prevProps.options !== this.props.options); // let forceUpdate = prevProps.options !== this.props.options; // console.log('forceUpdate', forceUpdate); // if(prevProps.options !== this.props.options) { // // console.log(`${this.constructor.name}.componentDidUpdate`, prevProps.options, this.props.options) // this.refresh(); // } if(this.props.options !== prevProps.options) { // console.log(`${this.constructor.name}.componentDidUpdate`, prevProps.options, this.props.options, this.state) this.processOptions(); } } // refresh(force=true) { // if(force || !this.state.options) // this.setOptions(this.props.options); // // } // componentWillUnmount() { // this.getOverlay().removeCloseMenuCallback(this); // } renderContent() { throw new Error("Not Implemented"); } render() { return <ASUIContextMenuContext.Provider value={{overlay: this.getOverlay(), parentMenu: this}}> {this.renderContent()} </ASUIContextMenuContext.Provider> } // render() { // return <ASUIContextMenuContext.Provider // value={{overlay:this.getOverlay(), parentDropDown:this}}> // {this.renderDropDownContainer()} // </ASUIContextMenuContext.Provider> // // } processOptions() { let options = this.props.options; if (typeof options === "function") options = options(this); if (options instanceof Promise) { // console.log('options', options); options.then(options => { if(!options) { console.log("Promise resulted in empty options", options); options = [<ASUIMenuItem>Error: No options returned from promise</ASUIMenuItem>]; } this.setState({options: options}) }) // this.setState({options: [<ASUIMenuItem>Loading...</ASUIMenuItem>]}) // options = await options; } } getOptions() { let options = this.state.options || this.props.options; if (typeof options === "function") options = options(this); if(options instanceof Promise) { return [<ASUIMenuItem>Loading...</ASUIMenuItem>]; // throw new Error("Promise unsupported"); // this.setState({options: [<ASUIMenuItem>Loading...</ASUIMenuItem>]}) // options = await options; } return options; } getFilteredOptions() { let options = this.getOptions(); options = MenuOptionProcessor.processArray(options); // let positionSelected = this.state.positionSelected; let currentPosition = 0; options = options .filter(option => option.type !== React.Fragment) .map((option, i) => { // if(positionSelected === null && option.props.selected) // positionSelected = currentPosition; const props = { key: i, } if(option.type.prototype instanceof ASUIClickable) { props.position = currentPosition; if(!this.ref.options[currentPosition]) this.ref.options[currentPosition] = React.createRef(); props.ref = this.ref.options[currentPosition]; currentPosition++; } return React.cloneElement(option, props); }); if(!options || options.length === 0) return [<ASUIMenuItem>No Options</ASUIMenuItem>]; if(options.length < this.state.limit) return options; if(this.state.offset > 0) { options = options.slice(this.state.offset); options.unshift(<ASUIMenuBreak />) options.unshift(<ASUIMenuAction onAction={() => { this.setOffset(this.state.offset - this.state.limit); return false; }} >... Previous ...</ASUIMenuAction>) } if(options.length > this.state.limit) { options = options.slice(0, this.state.limit); options.push(<ASUIMenuBreak />) options.push(<ASUIMenuAction onAction={() => { this.setOffset(this.state.offset + this.state.limit) return false; }} >... Next ...</ASUIMenuAction>) } return options; } /** Actions **/ focus() { throw new Error("Not Implemented"); } closeDropDownMenu() { this.props.onClose(); } // async setOptions(options) { // if (typeof options === "function") // options = options(this); // if(options instanceof Promise) { // this.setState({options: [<ASUIMenuItem>Loading...</ASUIMenuItem>]}) // options = await options; // } // if (!options) { // console.warn("Empty options returned by ", this); // options = [<ASUIMenuItem>No Options</ASUIMenuItem>]; // } // // // let options = MenuOptionProcessor.processArray(options); // let positionSelected = this.state.positionSelected, currentPosition = 0; // options = options // .filter(option => option.type !== React.Fragment) // .map((option, i) => { // // // if(positionSelected === null && option.props.selected) // // positionSelected = currentPosition; // // const props = { // key: i, // } // if(option.type.prototype instanceof ASUIClickable) { // props.position = currentPosition; // this.ref.options[currentPosition] = React.createRef(); // props.ref = this.ref.options[currentPosition]; // currentPosition++; // // } // // return React.cloneElement(option, props); // }); // // // this.setState({ // options, // positionSelected: positionSelected || null, // positionCount: currentPosition // }) // } setOffset(offset) { if(offset < 0) offset = 0; this.setState({offset}) } /** Input **/ onWheel(e) { console.log(e.type, e); e.preventDefault(); let offset = parseInt(this.state.offset) || 0; offset += e.deltaY > 0 ? 1 : -1; this.setOffset(offset); } onKeyDown(e) { if(e.isDefaultPrevented()) return; e.stopPropagation(); e.preventDefault(); let positionSelected = this.state.positionSelected; if(positionSelected === null) positionSelected = -1; const optionRef = this.ref.options[positionSelected] ? this.ref.options[positionSelected].current : null; // console.info("onKeyDown", e.key, e.target, this.state.positionSelected, optionRef); switch(e.key) { case 'ArrowUp': positionSelected -= 1; if(positionSelected < 0) positionSelected = this.state.positionCount-1; this.setState({positionSelected}) break; case 'ArrowDown': positionSelected += 1; if(positionSelected >= this.state.positionCount) positionSelected = 0; this.setState({positionSelected}) break; case 'ArrowRight': if(optionRef instanceof ASUIMenuDropDown) { optionRef.openDropDownMenu(); } else { console.warn("No action for ", e.key); } // optionRef.openDropDownMenu() break; case 'Escape': case 'ArrowLeft': const parentRef = this.getParentDropdown(); this.closeDropDownMenu(); if(parentRef) parentRef.focus(); else this.getOverlay().restoreActiveElementFocus(); break; case '': case 'Enter': if(optionRef instanceof ASUIClickable) { optionRef.doAction(e); } else { console.warn("No action for ", e.key); } // optionRef.openDropDownMenu() break; default: console.info("Unhandled key: ", e.key, e.target); break; } } }