github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreatePathModal.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import React, { useEffect, useState } from "react"; 18 import { useNavigate } from "react-router-dom"; 19 import { connect, useSelector } from "react-redux"; 20 import { 21 Button, 22 CreateNewPathIcon, 23 InputBox, 24 Grid, 25 FormLayout, 26 Box, 27 } from "mds"; 28 import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; 29 import { modalStyleUtils } from "../../../../Common/FormComponents/common/styleLibrary"; 30 import { encodeURLString } from "../../../../../../common/utils"; 31 import { BucketObjectItem } from "./types"; 32 import { AppState, useAppDispatch } from "../../../../../../store"; 33 import { setModalErrorSnackMessage } from "../../../../../../systemSlice"; 34 35 interface ICreatePath { 36 modalOpen: boolean; 37 bucketName: string; 38 folderName: string; 39 onClose: () => any; 40 simplePath: string | null; 41 limitedSubPath?: boolean; 42 } 43 44 const CreatePathModal = ({ 45 modalOpen, 46 folderName, 47 bucketName, 48 onClose, 49 simplePath, 50 limitedSubPath, 51 }: ICreatePath) => { 52 const dispatch = useAppDispatch(); 53 const navigate = useNavigate(); 54 55 const [pathUrl, setPathUrl] = useState(""); 56 const [isFormValid, setIsFormValid] = useState<boolean>(false); 57 const [currentPath, setCurrentPath] = useState(bucketName); 58 59 const records = useSelector((state: AppState) => state.objectBrowser.records); 60 61 useEffect(() => { 62 if (simplePath) { 63 const newPath = `${bucketName}${ 64 !bucketName.endsWith("/") && !simplePath.startsWith("/") ? "/" : "" 65 }${simplePath}`; 66 67 setCurrentPath(newPath); 68 } 69 }, [simplePath, bucketName]); 70 71 const resetForm = () => { 72 setPathUrl(""); 73 }; 74 75 const createProcess = () => { 76 let folderPath = "/"; 77 78 if (simplePath) { 79 folderPath = simplePath.endsWith("/") ? simplePath : `${simplePath}/`; 80 } 81 82 const sharesName = (record: BucketObjectItem) => 83 record.name === folderPath + pathUrl; 84 85 if (records.findIndex(sharesName) !== -1) { 86 dispatch( 87 setModalErrorSnackMessage({ 88 errorMessage: "Folder cannot have the same name as an existing file", 89 detailedError: "", 90 }), 91 ); 92 return; 93 } 94 95 const cleanPathURL = pathUrl 96 .split("/") 97 .filter((splitItem) => splitItem.trim() !== "") 98 .join("/"); 99 100 if (folderPath.slice(0, 1) === "/") { 101 folderPath = folderPath.slice(1); //trim '/' 102 } 103 104 const newPath = `/browser/${bucketName}/${encodeURLString( 105 `${folderPath}${cleanPathURL}/`, 106 )}`; 107 108 navigate(newPath); 109 onClose(); 110 }; 111 112 useEffect(() => { 113 let valid = true; 114 if (pathUrl.trim().length === 0) { 115 valid = false; 116 } 117 setIsFormValid(valid); 118 }, [pathUrl]); 119 120 const inputChange = (e: React.ChangeEvent<HTMLInputElement>) => { 121 setPathUrl(e.target.value); 122 }; 123 124 const keyPressed = (e: any) => { 125 if (e.code === "Enter" && pathUrl !== "") { 126 createProcess(); 127 } 128 }; 129 130 return ( 131 <React.Fragment> 132 <ModalWrapper 133 modalOpen={modalOpen} 134 title="Choose or create a new path" 135 onClose={onClose} 136 titleIcon={<CreateNewPathIcon />} 137 > 138 <FormLayout withBorders={false} containerPadding={false}> 139 <Box className={"inputItem"} sx={{ display: "flex", gap: 8 }}> 140 <strong>Current Path:</strong> <br /> 141 <Box 142 sx={{ 143 textOverflow: "ellipsis", 144 whiteSpace: "nowrap", 145 overflow: "hidden", 146 fontSize: 14, 147 textAlign: "left", 148 }} 149 dir={"rtl"} 150 > 151 {currentPath} 152 </Box> 153 </Box> 154 <InputBox 155 value={pathUrl} 156 label={"New Folder Path"} 157 id={"folderPath"} 158 name={"folderPath"} 159 placeholder={"Enter the new Folder Path"} 160 onChange={inputChange} 161 onKeyPress={keyPressed} 162 required 163 tooltip={ 164 (limitedSubPath && 165 "You may only have write access on a limited set of subpaths within this path. Please carefully review your User permissions to understand the paths to which you may write.") || 166 "" 167 } 168 /> 169 <Grid item xs={12} sx={modalStyleUtils.modalButtonBar}> 170 <Button 171 id={"clear"} 172 type="button" 173 color="primary" 174 variant="regular" 175 onClick={resetForm} 176 label={"Clear"} 177 /> 178 <Button 179 id={"create"} 180 type="submit" 181 variant="callAction" 182 disabled={!isFormValid} 183 onClick={createProcess} 184 label={"Create"} 185 /> 186 </Grid> 187 </FormLayout> 188 </ModalWrapper> 189 </React.Fragment> 190 ); 191 }; 192 193 const mapStateToProps = ({ objectBrowser }: AppState) => ({ 194 simplePath: objectBrowser.simplePath, 195 }); 196 197 const connector = connect(mapStateToProps); 198 199 export default connector(CreatePathModal);