// Copyright (c) 2024 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 Button from '@mui/material/Button'; import DialogEx from './DialogEx'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { UiFileProperty } from './Lv2Plugin'; import FilePropertyDirectoryTree from './FilePropertyDirectoryTree'; import ButtonBase from '@mui/material/ButtonBase'; import Typography from '@mui/material/Typography' import Divider from '@mui/material/Divider'; import FolderIcon from '@mui/icons-material/Folder'; import HomeIcon from '@mui/icons-material/Home'; import { isDarkMode } from './DarkMode'; import IconButton from '@mui/material/IconButton'; class DirectoryTree { name: string = ""; path: string = ""; expanded: boolean = false; isProtected: boolean = true; children: DirectoryTree[] = []; constructor(tree: FilePropertyDirectoryTree, parentTree: (DirectoryTree | null) = null) { this.name = tree.directoryName; this.path = tree.directoryName; this.name = tree.displayName; this.isProtected = tree.isProtected; for (var treeChild of tree.children) { this.children.push(new DirectoryTree(treeChild, this)); } } canExpand(): boolean { return this.children.length !== 0; } expand(path: string): void { this.expanded = true; let currentNode: DirectoryTree = this; for (var segment of path.split('/')) { let found = false; for (var child of currentNode.children) { if (child.name === segment) { found = true; if (child.children.length !== 0) { child.expanded = true; } currentNode = child; break; } } if (!found) { break; } } } private static remove_(directoryTree: DirectoryTree, path: string): boolean { for (let i = 0; i < directoryTree.children.length; ++i) { let child = directoryTree.children[i]; if (child.path === path) { directoryTree.children.splice(i,1) return true; } if (DirectoryTree.remove_(child,path)) { return true; } } return false; } remove(path: string): boolean { return DirectoryTree.remove_(this,path); } find(path: string): DirectoryTree | null { let currentNode: DirectoryTree = this; for (var segment of path.split('/')) { let found = false; for (var child of currentNode.children) { if (child.name === segment) { found = true; currentNode = child; break; } } if (!found) { return null; } } return currentNode; } } export interface FilePropertyDirectorySelectDialogProps { open: boolean, dialogTitle: string, uiFileProperty: UiFileProperty; defaultPath: string, excludeDirectory: string, selectedFile: string, onOk: (path: string) => void, onClose: () => void }; export interface FilePropertyDirectorySelectDialogState { fullScreen: boolean; selectedPath: string; directoryTree?: DirectoryTree; hasSelection: boolean; isProtected: boolean; directoryTreeInvalidatecount: number; }; export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveComponent { private model: PiPedalModel; constructor(props: FilePropertyDirectorySelectDialogProps) { super(props); this.model = PiPedalModelFactory.getInstance(); this.state = { fullScreen: false, selectedPath: props.defaultPath, directoryTree: undefined, hasSelection: false, isProtected: true, directoryTreeInvalidatecount: 0 }; this.requestDirectoryTree(); } mounted: boolean = false; onWindowSizeChanged(width: number, height: number): void { this.setState({ fullScreen: height < 200 }) } componentDidMount() { super.componentDidMount(); this.mounted = true; } componentWillUnmount() { super.componentWillUnmount(); this.mounted = false; } private requestScroll = true; requestDirectoryTree() { if (!this.props.open) return; this.model.getFilePropertyDirectoryTree(this.props.uiFileProperty,this.props.selectedFile) .then((filePropertyDirectoryTree) => { let myTree = new DirectoryTree(filePropertyDirectoryTree); if (this.props.excludeDirectory.length !== 0) { myTree.remove(this.props.excludeDirectory); } myTree.expand(this.state.selectedPath); let selection = myTree.find(this.state.selectedPath); let hasSelection = !!selection; this.setState({ directoryTree: myTree, hasSelection: hasSelection, isProtected: selection ? selection.isProtected : false }); this.requestScroll = true; }) .catch((e) => { this.model.showAlert(e.toString()); this.setState({ hasSelection: false }); }); } componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot: any): void { super.componentDidUpdate?.(prevProps, prevState, snapshot); if (prevProps.open !== this.props.open) { if (this.props.open) { this.setState({ selectedPath: this.props.defaultPath, hasSelection: false }); this.requestDirectoryTree(); this.requestScroll = true; } } } onOK() { if (this.state.isProtected) { return; } if (this.state.hasSelection) { this.props.onOk(this.state.selectedPath); } } onClose() { this.props.onClose(); } private onToggleExpand(directoryTree: DirectoryTree) { directoryTree.expanded = !directoryTree.expanded; this.setState({directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount+1}); } private onTreeClick(directoryTree: DirectoryTree) { if (!this.state.directoryTree) return; this.state.directoryTree.expand(directoryTree.path); this.setState({ selectedPath: directoryTree.path, hasSelection: true, isProtected: directoryTree.isProtected, directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount + 1 }); } private renderTree_(directoryTree: DirectoryTree) { let ref: ((element: HTMLButtonElement | null) => void) | undefined = undefined; let selected = directoryTree.path === this.state.selectedPath; if (selected) { ref = (element) => { if (this.requestScroll) { this.requestScroll = false; } } } let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)"; if (isDarkMode()) { selectBg = selected ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.0)"; } let showToggle = directoryTree.canExpand() && directoryTree.path.length !== 0; return (
{ this.onToggleExpand(directoryTree); }} style={{opacity: showToggle ? 1: 0}} disabled={!showToggle} > { directoryTree.expanded && ( ) } { !directoryTree.expanded && ( ) } { this.onTreeClick(directoryTree); }}>
{ directoryTree.path === "" ? ( ) : ( ) } {directoryTree.name === "" ? "Home" : directoryTree.name}
{directoryTree.expanded && directoryTree.canExpand() && (
{ directoryTree.children.map( (child, index) => { return this.renderTree_(child); } ) }
) }
); } private renderTree() { if (!this.state.directoryTree) { return undefined; } return this.renderTree_(this.state.directoryTree); } render() { return ( { this.onClose(); }} aria-labelledby="Rename-dialog-title" fullScreen={this.state.fullScreen} style={{ userSelect: "none" }} onEnterKey={()=>{ this.onOK(); }} PaperProps={{ style: { minHeight: "80%", maxHeight: "80%", minWidth: "75%", maxWidth: "75%", overflowY: "visible" } }} > {this.props.dialogTitle}
{ this.renderTree() }
); } }