pathSelector.js
1 export class PathSelector { 2 constructor(uiService, menuLoop, fsService) { 3 this.ui = uiService; 4 this.loop = menuLoop; 5 this.fs = fsService; 6 7 this.pathMustExist = true; 8 this.loop.initialize(this.showPathSelector); 9 } 10 11 show = async (startingPath, pathMustExist) => { 12 this.startingPath = startingPath; 13 this.pathMustExist = pathMustExist; 14 this.roots = this.fs.getAvailableRoots(); 15 this.currentPath = this.splitPath(startingPath); 16 if (!this.hasValidRoot(this.currentPath)) { 17 this.currentPath = [this.roots[0]]; 18 } 19 20 await this.loop.showLoop(); 21 22 return this.resultingPath; 23 }; 24 25 showPathSelector = async () => { 26 this.showCurrent(); 27 await this.ui.askMultipleChoice("Select an option:", [ 28 { 29 label: "Enter path", 30 action: this.enterPath, 31 }, 32 { 33 label: "Go up one", 34 action: this.upOne, 35 }, 36 { 37 label: "Go down one", 38 action: this.downOne, 39 }, 40 { 41 label: "Create new folder here", 42 action: this.createSubDir, 43 }, 44 { 45 label: "Select this path", 46 action: this.selectThisPath, 47 }, 48 { 49 label: "Cancel", 50 action: this.cancel, 51 }, 52 ]); 53 }; 54 55 splitPath = (str) => { 56 var result = this.dropEmptyParts(str.replaceAll("\\", "/").split("/")); 57 if (str.startsWith("/") && this.roots.includes("/")) { 58 result = ["/", ...result]; 59 } 60 return result; 61 }; 62 63 dropEmptyParts = (parts) => { 64 return parts.filter((part) => part.length > 0); 65 }; 66 67 combine = (parts) => { 68 const toJoin = this.dropEmptyParts(parts); 69 if (toJoin.length == 1) return toJoin[0]; 70 var result = this.fs.pathJoin(toJoin); 71 if (result.startsWith("//")) { 72 result = result.substring(1); 73 } 74 return result; 75 }; 76 77 combineWith = (parts, extra) => { 78 const toJoin = this.dropEmptyParts(parts); 79 if (toJoin.length == 1) return this.fs.pathJoin([toJoin[0], extra]); 80 return this.fs.pathJoin([...toJoin, extra]); 81 }; 82 83 showCurrent = () => { 84 const len = this.currentPath.length; 85 this.ui.showInfoMessage( 86 `Current path: [${len}]\n` + this.combine(this.currentPath), 87 ); 88 89 if (len < 2) { 90 this.ui.showInfoMessage( 91 "Warning - Known issue:\n" + 92 "Path selection does not work in root paths on some platforms.\n" + 93 'Use "Enter path" or "Create new folder" to navigate and create folders\n' + 94 "if this is the case for you.", 95 ); 96 } 97 }; 98 99 hasValidRoot = (checkPath) => { 100 if (checkPath.length < 1) return false; 101 var result = false; 102 this.roots.forEach(function (root) { 103 if (root.toLowerCase() == checkPath[0].toLowerCase()) { 104 result = true; 105 } 106 }); 107 return result; 108 }; 109 110 updateCurrentIfValidFull = (newFullPath) => { 111 if (this.pathMustExist && !this.fs.isDir(newFullPath)) { 112 this.ui.showErrorMessage("The path does not exist."); 113 return; 114 } 115 this.updateCurrentIfValidParts(this.splitPath(newFullPath)); 116 }; 117 118 updateCurrentIfValidParts = (newParts) => { 119 if (!this.hasValidRoot(newParts)) { 120 this.ui.showErrorMessage("The path has no valid root."); 121 return; 122 } 123 this.currentPath = newParts; 124 }; 125 126 enterPath = async () => { 127 const newPath = await this.ui.askPrompt("Enter Path:"); 128 this.updateCurrentIfValidFull(newPath); 129 }; 130 131 upOne = () => { 132 const newParts = this.currentPath.slice(0, this.currentPath.length - 1); 133 this.updateCurrentIfValidParts(newParts); 134 }; 135 136 isSubDir = (entry) => { 137 const newPath = this.combineWith(this.currentPath, entry); 138 return this.fs.isDir(newPath); 139 }; 140 141 getSubDirOptions = () => { 142 const fullPath = this.combine(this.currentPath); 143 try { 144 const entries = this.fs.readDir(fullPath); 145 return entries.filter((entry) => this.isSubDir(entry)); 146 } catch { 147 return []; 148 } 149 }; 150 151 downOne = async () => { 152 const options = this.getSubDirOptions(); 153 if (options.length == 0) { 154 this.ui.showInfoMessage("There are no subdirectories here."); 155 return; 156 } 157 158 var selected = ""; 159 var uiOptions = []; 160 options.forEach(function (option) { 161 uiOptions.push({ 162 label: option, 163 action: () => { 164 selected = option; 165 }, 166 }); 167 }); 168 169 await this.ui.askMultipleChoice("Select an subdir", uiOptions); 170 171 if (selected.length < 1) return; 172 this.updateCurrentIfValidParts([...this.currentPath, selected]); 173 }; 174 175 createSubDir = async () => { 176 const name = await this.ui.askPrompt("Enter name:"); 177 if (name.length < 1) return; 178 const newPath = [...this.currentPath, name]; 179 if (this.pathMustExist) { 180 this.fs.makeDir(this.combine(newPath)); 181 } 182 this.updateCurrentIfValidParts(newPath); 183 }; 184 185 selectThisPath = async () => { 186 this.resultingPath = this.combine(this.currentPath); 187 this.loop.stopLoop(); 188 }; 189 190 cancel = async () => { 191 this.resultingPath = this.startingPath; 192 this.loop.stopLoop(); 193 }; 194 }