FileTree.hx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. package hide.view;
  2. typedef ExtensionOptions = {
  3. ?icon : String,
  4. ?createNew : String,
  5. };
  6. typedef ExtensionDesc = {
  7. var component : String;
  8. var extensions : Array<String>;
  9. var options : ExtensionOptions;
  10. }
  11. class FileTree extends FileView {
  12. var tree : hide.comp.IconTree<String>;
  13. var ignorePatterns : Array<EReg> = [];
  14. public function new(state) {
  15. super(state);
  16. var exclPatterns : Array<String> = ide.currentConfig.get("filetree.excludes", []);
  17. for(pat in exclPatterns)
  18. ignorePatterns.push(new EReg(pat, "i"));
  19. if( state.path == null ) {
  20. ide.chooseDirectory(function(dir) {
  21. if( dir == null ) {
  22. close();
  23. return;
  24. }
  25. state.path = dir.split("\\").join("/")+"/";
  26. saveState();
  27. rebuild();
  28. },true);
  29. }
  30. keys.register("search", function() tree.openFilter());
  31. }
  32. override function canSave() {
  33. return false;
  34. }
  35. static function getExtension( file : String ) {
  36. var ext = new haxe.io.Path(file).ext;
  37. if( ext == null ) return null;
  38. ext = ext.toLowerCase();
  39. if( ext == "json" ) {
  40. try {
  41. var obj : Dynamic = haxe.Json.parse(sys.io.File.getContent(file));
  42. if( obj.type != null && Std.is(obj.type, String) ) {
  43. var e = EXTENSIONS.get("json." + obj.type);
  44. if( e != null ) return e;
  45. }
  46. } catch( e : Dynamic ) {
  47. }
  48. }
  49. return EXTENSIONS.get(ext);
  50. }
  51. override function getTitle() {
  52. if( state.path == "" )
  53. return "Resources";
  54. if( state.path == null )
  55. return "";
  56. return super.getTitle();
  57. }
  58. override function onFileChanged(wasDeleted:Bool, rebuildView:Bool = true) {
  59. }
  60. override function onDisplay() {
  61. if( state.path == null ) return;
  62. var panel = new Element("<div class='hide-scroll'>").appendTo(element);
  63. tree = new hide.comp.IconTree(null,panel);
  64. tree.async = true;
  65. tree.allowRename = true;
  66. tree.saveDisplayKey = saveDisplayKey;
  67. tree.element.addClass("small");
  68. tree.get = function(path) {
  69. if( path == null ) path = "";
  70. var basePath = getFilePath(path);
  71. var content = new Array<hide.comp.IconTree.IconTreeItem<String>>();
  72. for( c in sys.FileSystem.readDirectory(basePath) ) {
  73. var fullPath = basePath + "/" + c;
  74. if( isIgnored(fullPath) ) continue;
  75. var isDir = sys.FileSystem.isDirectory(fullPath);
  76. var ext = getExtension(fullPath);
  77. var id = ( path == "" ? c : path+"/"+c );
  78. content.push({
  79. value : id,
  80. text : c,
  81. icon : "ico ico-" + (isDir ? "folder" : (ext != null && ext.options.icon != null ? ext.options.icon : "file-text")),
  82. children : isDir,
  83. });
  84. }
  85. watch(basePath, function() rebuild(),{checkDelete:true});
  86. content.sort(function(a,b) { if( a.children != b.children ) return a.children?-1:1; return Reflect.compare(a.text,b.text); });
  87. return content;
  88. };
  89. tree.onRename = onRename;
  90. element.contextmenu(function(e) {
  91. var current = tree.getCurrentOver();
  92. if( current != null )
  93. tree.setSelection([current]);
  94. e.preventDefault();
  95. var allowedNew : Array<String> = config.get("filetree.allowednew");
  96. function allowed( ext : String ) return allowedNew.indexOf(ext) >= 0 || allowedNew.indexOf("*") >= 0;
  97. var newMenu = [for( e in EXTENSIONS ) if( e.options.createNew != null && Lambda.exists(e.extensions, allowed) ) {
  98. label : e.options.createNew,
  99. click : createNew.bind(current, e),
  100. icon : e.options.icon,
  101. }];
  102. if( allowed("dir") )
  103. newMenu.unshift({
  104. label : "Directory",
  105. click : createNew.bind(current, { options : { createNew : "Directory" }, extensions : null, component : null }),
  106. icon : "folder",
  107. });
  108. new hide.comp.ContextMenu([
  109. { label : "New..", menu:newMenu },
  110. { label : "", isSeparator: true },
  111. { label : "Explore", enabled : current != null, click : function() { onExploreFile(current); } },
  112. { label : "Copy Path", enabled : current != null, click : function() { ide.setClipboard(current); } },
  113. { label : "", isSeparator: true },
  114. { label : "Clone", enabled : current != null, click : function() {
  115. try {
  116. if (onCloneFile(current)) {
  117. tree.refresh();
  118. }
  119. } catch (e : Dynamic) {
  120. js.Browser.window.alert(e);
  121. }
  122. } },
  123. { label : "Rename", enabled : current != null, click : function() {
  124. try {
  125. onRenameFile(current);
  126. } catch (e : Dynamic) {
  127. js.Browser.window.alert(e);
  128. }
  129. } },
  130. { label : "Move", enabled : current != null, click : function() {
  131. ide.chooseDirectory(function(dir) {
  132. onRename(current, "/"+dir+"/"+current.split("/").pop());
  133. });
  134. }},
  135. { label : "Delete", enabled : current != null, click : function() {
  136. if( js.Browser.window.confirm("Delete " + current + "?") ) {
  137. onDeleteFile(current);
  138. tree.refresh();
  139. }
  140. }},
  141. ]);
  142. });
  143. tree.onDblClick = onOpenFile;
  144. tree.init();
  145. }
  146. function onRenameFile( path : String ) {
  147. var newFilename = ide.ask("New name:", path.substring( path.lastIndexOf("/") + 1 ));
  148. while ( newFilename != null && sys.FileSystem.exists(ide.getPath(newFilename))) {
  149. newFilename = ide.ask("This file already exists. Another new name:");
  150. }
  151. if (newFilename == null) {
  152. return false;
  153. }
  154. onRename(path, newFilename);
  155. return true;
  156. }
  157. function onRename(path:String, name:String) {
  158. var parts = path.split("/");
  159. parts.pop();
  160. for( n in name.split("/") ) {
  161. if( n == ".." )
  162. parts.pop();
  163. else
  164. parts.push(n);
  165. }
  166. var newPath = name.charAt(0) == "/" ? name.substr(1) : parts.join("/");
  167. if( newPath == path )
  168. return false;
  169. if( sys.FileSystem.exists(ide.getPath(newPath)) ) {
  170. function addPath(path:String,rand:String) {
  171. var p = path.split(".");
  172. if( p.length > 1 )
  173. p[p.length-2] += rand;
  174. else
  175. p[p.length-1] += rand;
  176. return p.join(".");
  177. }
  178. if( path.toLowerCase() == newPath.toLowerCase() ) {
  179. // case change
  180. var rand = "__tmp"+Std.random(10000);
  181. onRename(path, "/"+addPath(path,rand));
  182. onRename(addPath(path,rand), name);
  183. } else {
  184. if( !ide.confirm(newPath+" already exists, invert files?") )
  185. return false;
  186. var rand = "__tmp"+Std.random(10000);
  187. onRename(path, "/"+addPath(path,rand));
  188. onRename(newPath, "/"+path);
  189. onRename(addPath(path,rand), name);
  190. }
  191. return false;
  192. }
  193. var isDir = sys.FileSystem.isDirectory(ide.getPath(path));
  194. var wasRenamed = false;
  195. var isSVNRepo = sys.FileSystem.exists(ide.projectDir+"/.svn") || js.node.ChildProcess.spawnSync("svn",["info"], { cwd : ide.resourceDir }).status == 0; // handle not root dirs
  196. if( isSVNRepo ) {
  197. if( js.node.ChildProcess.spawnSync("svn",["--version"]).status != 0 ) {
  198. if( isDir && !ide.confirm("Renaming a SVN directory, but 'svn' system command was not found. Continue ?") )
  199. return false;
  200. } else {
  201. var cwd = Sys.getCwd();
  202. Sys.setCwd(ide.resourceDir);
  203. var code = Sys.command("svn",["rename",path,newPath]);
  204. Sys.setCwd(cwd);
  205. if( code == 0 )
  206. wasRenamed = true;
  207. else {
  208. if( !ide.confirm("SVN rename failure, perform file rename ?") )
  209. return false;
  210. }
  211. }
  212. }
  213. if( !wasRenamed )
  214. sys.FileSystem.rename(ide.getPath(path), ide.getPath(newPath));
  215. var changed = false;
  216. function filter(p:String) {
  217. if( p == null )
  218. return null;
  219. if( p == path ) {
  220. changed = true;
  221. return newPath;
  222. }
  223. if( p == "/"+path ) {
  224. changed = true;
  225. return "/"+newPath;
  226. }
  227. if( isDir ) {
  228. if( StringTools.startsWith(p,path+"/") ) {
  229. changed = true;
  230. return newPath + p.substr(path.length);
  231. }
  232. if( StringTools.startsWith(p,"/"+path+"/") ) {
  233. changed = true;
  234. return "/"+newPath + p.substr(path.length+1);
  235. }
  236. }
  237. return p;
  238. }
  239. ide.filterPrefabs(function(p:hrt.prefab.Prefab) {
  240. changed = false;
  241. p.source = filter(p.source);
  242. var h = p.getHideProps();
  243. if( h.onResourceRenamed != null )
  244. h.onResourceRenamed(filter);
  245. else {
  246. var visited = new Array<Dynamic>();
  247. function browseRec(obj:Dynamic) : Dynamic {
  248. switch( Type.typeof(obj) ) {
  249. case TObject:
  250. if( visited.indexOf(obj) >= 0 ) return null;
  251. visited.push(obj);
  252. for( f in Reflect.fields(obj) ) {
  253. var v : Dynamic = Reflect.field(obj, f);
  254. v = browseRec(v);
  255. if( v != null ) Reflect.setField(obj, f, v);
  256. }
  257. case TClass(Array):
  258. if( visited.indexOf(obj) >= 0 ) return null;
  259. visited.push(obj);
  260. var arr : Array<Dynamic> = obj;
  261. for( i in 0...arr.length ) {
  262. var v : Dynamic = arr[i];
  263. v = browseRec(v);
  264. if( v != null ) arr[i] = v;
  265. }
  266. case TClass(String):
  267. return filter(obj);
  268. default:
  269. }
  270. return null;
  271. }
  272. for( f in Reflect.fields(p) ) {
  273. var v = browseRec(Reflect.field(p,f));
  274. if( v != null ) Reflect.setField(p,f,v);
  275. }
  276. }
  277. return changed;
  278. });
  279. changed = false;
  280. var tmpSheets = [];
  281. for( sheet in ide.database.sheets ) {
  282. if( sheet.props.dataFiles != null && sheet.lines == null ) {
  283. // we already updated prefabs, no need to load data files
  284. tmpSheets.push(sheet);
  285. @:privateAccess sheet.sheet.lines = [];
  286. }
  287. for( c in sheet.columns ) {
  288. switch( c.type ) {
  289. case TFile:
  290. for( o in sheet.getLines() ) {
  291. var v : Dynamic = filter(Reflect.field(o, c.name));
  292. if( v != null ) Reflect.setField(o, c.name, v);
  293. }
  294. default:
  295. }
  296. }
  297. }
  298. if( changed ) {
  299. ide.saveDatabase();
  300. hide.comp.cdb.Editor.refreshAll(true);
  301. }
  302. for( sheet in tmpSheets )
  303. @:privateAccess sheet.sheet.lines = null;
  304. var dataDir = new haxe.io.Path(path);
  305. if( dataDir.ext != "dat" ) {
  306. dataDir.ext = "dat";
  307. var dataPath = dataDir.toString();
  308. if( sys.FileSystem.isDirectory(ide.getPath(dataPath)) ) {
  309. var destPath = new haxe.io.Path(name);
  310. destPath.ext = "dat";
  311. onRename(dataPath, destPath.toString());
  312. }
  313. }
  314. return true;
  315. }
  316. public static function exploreFile(path : String) {
  317. var fullPath = sys.FileSystem.absolutePath(path);
  318. Sys.command("explorer.exe /select," + fullPath);
  319. }
  320. function onExploreFile( path : String ) {
  321. exploreFile(getFilePath(path));
  322. }
  323. function onCloneFile( path : String ) {
  324. var sourcePath = getFilePath(path);
  325. var nameNewFile = ide.ask("New filename:", new haxe.io.Path(sourcePath).file);
  326. if (nameNewFile == null || nameNewFile.length == 0) {
  327. return false;
  328. }
  329. var targetPath = new haxe.io.Path(sourcePath).dir + "/" + nameNewFile;
  330. if ( sys.FileSystem.exists(targetPath) ) {
  331. throw "File already exists";
  332. }
  333. if( sys.FileSystem.isDirectory(sourcePath) ) {
  334. sys.FileSystem.createDirectory(targetPath + "/");
  335. for( f in sys.FileSystem.readDirectory(sourcePath) ) {
  336. sys.io.File.saveBytes(targetPath + "/" + f, sys.io.File.getBytes(sourcePath + "/" + f));
  337. }
  338. } else {
  339. var extensionNewFile = getExtension(targetPath);
  340. if (extensionNewFile == null) {
  341. var extensionSourceFile = getExtension(sourcePath).extensions[0];
  342. if (extensionSourceFile != null) {
  343. targetPath = targetPath + "." + extensionSourceFile;
  344. }
  345. }
  346. sys.io.File.saveBytes(targetPath, sys.io.File.getBytes(sourcePath));
  347. }
  348. return true;
  349. }
  350. function onDeleteFile( path : String ) {
  351. var fullPath = getFilePath(path);
  352. if( sys.FileSystem.isDirectory(fullPath) ) {
  353. for( f in sys.FileSystem.readDirectory(fullPath) )
  354. onDeleteFile(path + "/" + f);
  355. sys.FileSystem.deleteDirectory(fullPath);
  356. } else
  357. sys.FileSystem.deleteFile(fullPath);
  358. }
  359. function getFilePath(path:String) {
  360. if( path == "" )
  361. return ide.getPath(state.path).substr(0, -1);
  362. return ide.getPath(state.path) + path;
  363. }
  364. function onOpenFile( path : String ) {
  365. var fullPath = getFilePath(path);
  366. if( sys.FileSystem.isDirectory(fullPath) )
  367. return false;
  368. var ext = getExtension(fullPath);
  369. if( ext == null )
  370. return false;
  371. ide.openFile(fullPath);
  372. tree.closeFilter();
  373. return true;
  374. }
  375. public function revealNode( path : String ) {
  376. function doreveal() {
  377. tree.revealNode(path);
  378. tree.setSelection([path]);
  379. }
  380. if( tree.async ) {
  381. tree.async = false;
  382. tree.refresh(doreveal);
  383. }
  384. else
  385. doreveal();
  386. }
  387. function createNew( basePath : String, ext : ExtensionDesc ) {
  388. if( basePath == null )
  389. basePath = "";
  390. var fullPath = getFilePath(basePath);
  391. if( !sys.FileSystem.isDirectory(fullPath) ) {
  392. basePath = new haxe.io.Path(basePath).dir;
  393. if( basePath == null ) basePath = "";
  394. fullPath = getFilePath(basePath);
  395. }
  396. var file = ide.ask(ext.options.createNew + " name:");
  397. if( file == null ) return;
  398. if( file.indexOf(".") < 0 && ext.extensions != null )
  399. file += "." + ext.extensions[0].split(".").shift();
  400. if( sys.FileSystem.exists(fullPath + "/" + file) ) {
  401. ide.error("File '" + file+"' already exists");
  402. createNew(basePath, ext);
  403. return;
  404. }
  405. // directory
  406. if( ext.component == null ) {
  407. sys.FileSystem.createDirectory(fullPath + "/" + file);
  408. return;
  409. }
  410. var view : hide.view.FileView = Type.createEmptyInstance(Type.resolveClass(ext.component));
  411. view.ide = ide;
  412. sys.io.File.saveBytes(fullPath + "/" + file, view.getDefaultContent());
  413. var fpath = basePath == "" ? file : basePath + "/" + file;
  414. tree.refresh(function() tree.setSelection([fpath]));
  415. onOpenFile(fpath);
  416. }
  417. function isIgnored( fullpath : String ) {
  418. for(pat in ignorePatterns) {
  419. if(pat.match(fullpath))
  420. return true;
  421. }
  422. return false;
  423. }
  424. static var EXTENSIONS = new Map<String,ExtensionDesc>();
  425. public static function registerExtension<T>( c : Class<hide.ui.View<T>>, extensions : Array<String>, ?options : ExtensionOptions ) {
  426. hide.ui.View.register(c);
  427. if( options == null ) options = {};
  428. var obj = { component : Type.getClassName(c), options : options, extensions : extensions };
  429. for( e in extensions )
  430. EXTENSIONS.set(e, obj);
  431. return null;
  432. }
  433. static var _ = hide.ui.View.register(FileTree, { width : 350, position : Left });
  434. }