// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import React, { Component } from 'react'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { BankIndexEntry, BankIndex } from './Banks'; import Button from "@mui/material/Button"; import ButtonBase from "@mui/material/ButtonBase"; import Slide, {SlideProps} from '@mui/material/Slide'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import { Theme, createStyles } from '@mui/material/styles'; import { WithStyles,withStyles } from "@mui/styles"; import DraggableGrid, { ScrollDirection } from './DraggableGrid'; import Fade from '@mui/material/Fade'; import SelectHoverBackground from './SelectHoverBackground'; import CloseIcon from '@mui/icons-material/Close'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import EditIcon from '@mui/icons-material/Edit'; import RenameDialog from './RenameDialog'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import DialogEx from './DialogEx'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; interface BankDialogProps extends WithStyles { show: boolean; isEditDialog: boolean; onDialogClose: () => void; }; interface BankDialogState { banks: BankIndex; showActionBar: boolean; selectedItem: number; filenameDialogOpen: boolean; filenameSaveAs: boolean; moreMenuAnchorEl: HTMLElement | null; showDeletePrompt: boolean; }; const styles = (theme: Theme) => createStyles({ dialogAppBar: { position: 'relative', top: 0, left: 0 }, dialogActionBar: { position: 'relative', top: 0, left: 0, background: "black" }, dialogTitle: { marginLeft: theme.spacing(2), flex: 1, }, itemFrame: { display: "flex", flexDirection: "row", flexWrap: "nowrap", width: "100%", height: "56px", alignItems: "center", textAlign: "left", justifyContent: "center", paddingLeft: 8 }, iconFrame: { flex: "0 0 auto", }, itemIcon: { width: 24, height: 24, margin: 12, opacity: 0.6 }, itemLabel: { flex: "1 1 1px", marginLeft: 8 } }); const Transition = React.forwardRef(function Transition( props: SlideProps, ref: React.Ref ) { return (); }); const BankDialog = withStyles(styles, { withTheme: true })( class extends Component { model: PiPedalModel; refUpload: React.RefObject; constructor(props: BankDialogProps) { super(props); this.refUpload = React.createRef(); this.model = PiPedalModelFactory.getInstance(); this.handleDialogClose = this.handleDialogClose.bind(this); let banks = this.model.banks.get(); this.state = { banks: banks, showActionBar: false, selectedItem: banks.selectedBank, filenameDialogOpen: false, filenameSaveAs: false, moreMenuAnchorEl: null, showDeletePrompt: false }; this.handleBanksChanged = this.handleBanksChanged.bind(this); } onMoreClick(e: React.SyntheticEvent): void { this.setState({ moreMenuAnchorEl: e.currentTarget as HTMLElement }) } handleDownloadBank() { this.handleMoreClose(); this.model.download("downloadBank", this.state.selectedItem); } async uploadFiles(fileList: FileList): Promise { let uploadAfter = this.state.selectedItem; try { for (let i = 0; i < fileList.length; ++i) { uploadAfter = await this.model.uploadBank(fileList[i], uploadAfter); } } catch (error) { this.model.showAlert(error +""); }; return uploadAfter; } handleUpload(e: any) { if (!e.target.files) return; if (e.target.files.length === 0) return; var fileList = e.target.files; this.uploadFiles(fileList) .then((newSelection) => { e.target.value = ""; // clear the file list so we can get another change notice. this.setState({ selectedItem: newSelection }); }) .catch(err => { e.target.value = ""; // clear the file list so we can get another change notice. this.model.showAlert(err); }); } handleUploadBank() { if (this.refUpload.current) { this.refUpload.current.click(); } this.handleMoreClose(); } handleMoreClose(): void { this.setState({ moreMenuAnchorEl: null }); } selectItemAtIndex(index: number) { let instanceId = this.state.banks.entries[index].instanceId; this.setState({ selectedItem: instanceId }); } isEditMode() { return this.state.showActionBar || this.props.isEditDialog; } handleBanksChanged() { let banks = this.model.banks.get(); if (!banks.areEqual(this.state.banks, false)) // avoid a bunch of peculiar effects if we update while a drag is in progress { // if we don't have a valid selection, then use the current bank. if (this.state.banks.getEntry(this.state.selectedItem) == null) { this.setState({ banks: banks, selectedItem: banks.selectedBank }); } else { this.setState({ banks: banks }); } } } mounted: boolean = false; hasHooks: boolean = false; componentDidUpdate() { } componentDidMount() { this.model.banks.addOnChangedHandler(this.handleBanksChanged); this.handleBanksChanged(); // scroll selected item into view. this.mounted = true; } componentWillUnmount() { this.model.banks.removeOnChangedHandler(this.handleBanksChanged); this.mounted = false; } getSelectedIndex() { let instanceId = this.isEditMode() ? this.state.selectedItem : this.state.banks.selectedBank; let banks = this.state.banks; for (let i = 0; i < banks.entries.length; ++i) { if (banks.entries[i].instanceId === instanceId) return i; } return -1; } handleDeleteClick() { if (!this.state.selectedItem) return; this.setState({ showDeletePrompt: true }); } handleDeletePromptClose() { this.setState({ showDeletePrompt: false }); } handleDeletePromptOk() { this.handleDeletePromptClose(); let selectedItem = this.state.selectedItem; if (selectedItem !== -1) { this.model.deleteBankItem(selectedItem) .then((newSelection: number) => { this.setState({ selectedItem: newSelection }); }) .catch((error) => { this.model.showAlert(error); }); } } handleDialogClose() { this.props.onDialogClose(); } handleItemClick(instanceId: number): void { if (this.isEditMode()) { this.setState({ selectedItem: instanceId }); } else { this.model.openBank(instanceId); this.props.onDialogClose(); } } showActionBar(show: boolean): void { this.setState({ showActionBar: show }); } mapElement(el: any): React.ReactNode { let bankEntry = el as BankIndexEntry; let classes = this.props.classes; let selectedItem = this.isEditMode() ? this.state.selectedItem : this.state.banks.selectedBank; return (
this.handleItemClick(bankEntry.instanceId)} >
{bankEntry.name}
); } moveElement(from: number, to: number): void { let newBanks = this.state.banks.clone(); newBanks.moveBank(from, to); this.setState({ banks: newBanks, selectedItem: newBanks.entries[to].instanceId }); this.model.moveBank(from, to) .catch((error) => { this.model.showAlert(error); }); } getSelectedName(): string { let item = this.state.banks.getEntry(this.state.selectedItem); if (item) return item.name; return ""; } handleRenameClick() { let item = this.state.banks.getEntry(this.state.selectedItem); if (item) { this.setState({ filenameDialogOpen: true, filenameSaveAs: false }); } } handleRenameOk(text: string) { let item = this.state.banks.getEntry(this.state.selectedItem); if (!item) return; if (item.name !== text) { if (this.state.banks.hasName(text)) { this.model.showAlert("A bank with that name already exists."); } this.model.renameBank(this.state.selectedItem, text) .catch((error) => { this.onError(error); }); } this.setState({ filenameDialogOpen: false }); } handleSaveAsOk(text: string) { let item = this.state.banks.getEntry(this.state.selectedItem); if (item) { if (item.name !== text) { if (this.state.banks.hasName(text)) { this.model.showAlert("A bank with that name already exists."); return; } this.model.saveBankAs(this.state.selectedItem, text) .then((newSelection) => { this.setState({ selectedItem: newSelection }); }) .catch((error) => { this.onError(error); }); } } this.setState({ filenameDialogOpen: false }); } handleCopy() { let item = this.state.banks.getEntry(this.state.selectedItem); if (item) { this.setState({ filenameDialogOpen: true, filenameSaveAs: true }); } } onError(error: string): void { this.model?.showAlert(error); } getSelectedBankName() { try { return this.model.banks.get().getEntry(this.state.selectedItem)!.name; } catch (error) { return ""; } } render() { let classes = this.props.classes; let actionBarClass = this.props.isEditDialog ? classes.dialogAppBar : classes.dialogActionBar; let defaultSelectedIndex = this.getSelectedIndex(); return ( { this.handleDialogClose() }} TransitionComponent={Transition} style={{userSelect: "none"}} >
Banks this.showActionBar(true)} > { e.stopPropagation(); e.preventDefault(); }} > {(!this.props.isEditDialog) ? ( this.showActionBar(false)} aria-label="close"> ) : ( )} Banks {(this.state.banks.getEntry(this.state.selectedItem) != null) && (
{ this.setState({ filenameDialogOpen: false }) }} onOk={(text: string) => { if (this.state.filenameSaveAs) { this.handleSaveAsOk(text); } else { this.handleRenameOk(text); } } } /> this.handleDeleteClick()} > Delete { this.onMoreClick(e) }} > this.handleMoreClose()} TransitionComponent={Fade} > { this.handleDownloadBank(); }} > Download bank
) }
this.showActionBar(true)} canDrag={this.isEditMode()} onDragStart={(index, x, y) => { this.selectItemAtIndex(index) }} moveElement={(from, to) => { this.moveElement(from, to); }} scroll={ScrollDirection.Y} defaultSelectedIndex={defaultSelectedIndex} > { this.state.banks.entries.map((element) => { return this.mapElement(element); }) }
this.handleUpload(e)} style={{ display: "none" }} /> this.handleDeletePromptClose()} style={{userSelect: "none"}}> Are you sure you want to delete bank '{this.getSelectedBankName()}'?
); } } ); export default BankDialog;