storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/browser/app/js/objects/Path.js (about) 1 /* 2 * MinIO Cloud Storage (C) 2016, 2018, 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import React from "react" 18 import { connect } from "react-redux" 19 import ClickOutHandler from "react-onclickout" 20 import { OverlayTrigger, Tooltip } from "react-bootstrap" 21 import { getCurrentBucket } from "../buckets/selectors" 22 import * as actionsObjects from "./actions" 23 import * as actionsBuckets from "../buckets/actions" 24 25 export class Path extends React.Component { 26 constructor(props) { 27 super(props) 28 this.state = { 29 isEditing: false, 30 path: "" 31 } 32 } 33 stopEditing() { 34 this.setState({ 35 isEditing: false 36 }) 37 } 38 onPrefixClick(e, prefix) { 39 e.preventDefault() 40 const { selectPrefix } = this.props 41 selectPrefix(prefix) 42 } 43 onEditClick(e) { 44 e.preventDefault() 45 const { currentBucket, currentPrefix } = this.props 46 this.setState( 47 { 48 isEditing: true, 49 path: `${currentBucket}/${currentPrefix}` 50 }, 51 () => { 52 // focus on input and move cursor to the end 53 this.pathInput.focus() 54 this.pathInput.setSelectionRange( 55 this.state.path.length, 56 this.state.path.length 57 ) 58 } 59 ) 60 } 61 onKeyDown(e) { 62 // When Esc key is pressed 63 if (e.keyCode === 27) { 64 this.stopEditing() 65 } 66 } 67 onInputClickOut() { 68 this.stopEditing() 69 } 70 bucketExists(bucketName) { 71 const { buckets } = this.props 72 return buckets.includes(bucketName) 73 } 74 async onSubmit(e) { 75 e.preventDefault() 76 const { makeBucket, selectBucket } = this.props 77 // all paths need to end in slash to display contents properly 78 let path = this.state.path 79 if (!path.endsWith("/")) { 80 path += "/" 81 } 82 const splittedPath = path.split("/") 83 if (splittedPath.length > 0) { 84 // prevent bucket name from being empty 85 if (splittedPath[0]) { 86 const bucketName = splittedPath[0] 87 const prefix = splittedPath.slice(1).join("/") 88 if (!this.bucketExists(bucketName)) { 89 await makeBucket(bucketName) 90 } 91 // check updated buckets and don't proceed on invalid inputs 92 if (this.bucketExists(bucketName)) { 93 // then select bucket with prefix 94 selectBucket(bucketName, prefix) 95 } 96 this.stopEditing() 97 } 98 } 99 } 100 render() { 101 const pathTooltip = <Tooltip id="tt-path">Choose or create new path</Tooltip> 102 const { currentBucket, currentPrefix } = this.props 103 let dirPath = [] 104 let path = "" 105 if (currentPrefix) { 106 path = currentPrefix.split("/").map((dir, i) => { 107 if (dir) { 108 dirPath.push(dir) 109 let dirPath_ = dirPath.join("/") + "/" 110 return ( 111 <span key={i}> 112 <a href="" onClick={e => this.onPrefixClick(e, dirPath_)}> 113 {dir} 114 </a> 115 </span> 116 ) 117 } 118 }) 119 } 120 return ( 121 <h2> 122 {this.state.isEditing ? ( 123 <ClickOutHandler onClickOut={() => this.onInputClickOut()}> 124 <form onSubmit={e => this.onSubmit(e)}> 125 <input 126 className="form-control form-control--path" 127 type="text" 128 placeholder="Choose or create new path" 129 ref={node => (this.pathInput = node)} 130 onKeyDown={e => this.onKeyDown(e)} 131 value={this.state.path} 132 onChange={e => this.setState({ path: e.target.value })} 133 /> 134 </form> 135 </ClickOutHandler> 136 ) : ( 137 <React.Fragment> 138 <span className="main"> 139 <a href="" onClick={e => this.onPrefixClick(e, "")}> 140 {currentBucket} 141 </a> 142 </span> 143 {path} 144 <OverlayTrigger placement="bottom" overlay={pathTooltip}> 145 <a href="" onClick={e => this.onEditClick(e)} className="fe-edit"> 146 <i className="fas fa-folder-plus" /> 147 </a> 148 </OverlayTrigger> 149 </React.Fragment> 150 )} 151 </h2> 152 ) 153 } 154 } 155 156 const mapStateToProps = state => { 157 return { 158 buckets: state.buckets.list, 159 currentBucket: getCurrentBucket(state), 160 currentPrefix: state.objects.currentPrefix 161 } 162 } 163 164 const mapDispatchToProps = dispatch => { 165 return { 166 makeBucket: bucket => dispatch(actionsBuckets.makeBucket(bucket)), 167 selectBucket: (bucket, prefix) => 168 dispatch(actionsBuckets.selectBucket(bucket, prefix)), 169 selectPrefix: prefix => dispatch(actionsObjects.selectPrefix(prefix)) 170 } 171 } 172 173 export default connect( 174 mapStateToProps, 175 mapDispatchToProps 176 )(Path)