2
0
Эх сурвалжийг харах

[fileBrowser] Context menu for fancyTree

Clément Espeute 2 сар өмнө
parent
commit
3f612b642a

+ 2 - 0
bin/style.css

@@ -5396,6 +5396,8 @@ fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name {
   height: 32px;
   display: block;
   text-align: center;
+  text-wrap: wrap;
+  word-break: break-all;
   text-overflow: ellipsis;
   overflow: hidden;
 }

+ 2 - 2
bin/style.less

@@ -6496,8 +6496,8 @@ fancy-gallery {
 					height: 32px;
 					display: block;
 					text-align: center;
-					//text-wrap: wrap;
-					//word-break: break-all;
+					text-wrap: wrap;
+					word-break: break-all;
 					text-overflow: ellipsis;
 					overflow: hidden;
 

+ 17 - 0
hide/comp/FancyTree.hx

@@ -101,6 +101,7 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 
 		var fancyTree = el.get(0);
 		fancyTree.onkeydown = inputHandler;
+		fancyTree.oncontextmenu = contextMenuHandler.bind(null);
 
 		scroll.onscroll = (e) -> queueRefresh();
 
@@ -164,6 +165,20 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 		onDrop: (target: TreeItem, where: DropOperation, dataTransfer: js.html.DataTransfer) -> Void
 	} = null;
 
+	/**
+		Called when the user right click an item (or the background) of the tree.
+		`item` will be null if the background was clicked. Default is do nothing
+	**/
+	public dynamic function onContextMenu(item: TreeItem, event : js.html.MouseEvent) {
+		event.stopPropagation();
+		event.preventDefault();
+	}
+
+	// Separate definition to onContextMenu to allow .bind()
+	function contextMenuHandler(item: TreeItem, event : js.html.MouseEvent) {
+		onContextMenu(item, event);
+	}
+
 	/**
 		Called when the user renamed the item via F2 / Context menu
 	**/
@@ -534,6 +549,8 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 				<fancy-tree-name></fancy-tree-name>
 			';
 
+			element.oncontextmenu = contextMenuHandler.bind(data.item);
+
 			var fold = element.querySelector(".caret");
 			fold.addEventListener("click", (e) -> {
 				toggleDataOpen(data);

+ 10 - 1
hide/tools/FileWatcher.hx

@@ -43,7 +43,16 @@ class FileWatcher {
 	public function resume() {
 		for( w in watches )
 			if( w.w == null && w.events.length > 0 ) {
-				initWatch(w);
+				try initWatch(w) catch( e : Dynamic ) {
+					// file does not exists, trigger a delayed event
+					haxe.Timer.delay(function() {
+						for( e in w.events.copy() )
+							if( isLive(w.events,e) && e.checkDel )
+								e.fun();
+					}, 0);
+					return;
+				}
+
 				var sign = getSignature(w.path);
 				for( f in w.events )
 					if( f.ignoreCheck != sign || w.isDir ) {

+ 8 - 0
hide/tools/IdeData.hx

@@ -168,6 +168,14 @@ class IdeData {
 		return resourceDir+"/"+relPath;
 	}
 
+	public function getRelPath(absPath: String) {
+		if (absPath == null)
+			return null;
+		if (!haxe.io.Path.isAbsolute(absPath))
+			return absPath;
+		return StringTools.replace(absPath, resourceDir + "/", "");
+	}
+
 	var lastDBContent = null;
 	function loadDatabase( ?checkExists ) {
 		var exists = fileExists(databaseFile);

+ 0 - 1
hide/tools/ThumbnailGenerator.hx

@@ -53,7 +53,6 @@ class MessageHandler {
 			}
 		}
 	}
-
 }
 
 @:access(hide.tools.FileManager)

+ 173 - 4
hide/view/FileBrowser.hx

@@ -103,7 +103,7 @@ class FileEntry {
 	public function getRelPath() {
 		if (this.parent == null) return "";
 		if (this.parent.parent == null) return this.name;
-		return this.parent.getPath() + "/" + this.name;
+		return this.parent.getRelPath() + "/" + this.name;
 	}
 
 	// sort directories before files, and then dirs and files alphabetically
@@ -192,6 +192,20 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 		galleryRefreshQueued = false;
 		hide.tools.FileManager.inst.clearRenderQueue();
 		currentSearch = [];
+
+		var validFolder = currentFolder;
+		while(validFolder != null && !sys.FileSystem.exists(validFolder.getPath())) {
+			validFolder = validFolder.parent;
+		}
+		if (validFolder == null) {
+			validFolder = root;
+		}
+		if (validFolder != currentFolder) {
+			currentFolder = validFolder;
+			fancyTree.clearSelection();
+			fancyTree.selectItem(currentFolder);
+		}
+
 		if (searchString.length == 0 && !collapseSubfolders && !filterEnabled) {
 			currentSearch = currentFolder.children;
 		} else {
@@ -235,6 +249,7 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				}
 			}
 
+
 			rec(currentFolder.children);
 
 			currentSearch.sort(FileEntry.compareFile);
@@ -257,6 +272,10 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 	}
 
 	override function onDisplay() {
+
+		keys.register("undo", function() undo.undo());
+		keys.register("redo", function() undo.redo());
+
 		root = new FileEntry("res", null, Dir, onFileChange);
 
 		root.refreshChildren();
@@ -339,8 +358,11 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				if (selection.length <= 0)
 					return false;
 				var ser = [];
+				ser.push(file.getPath());
 				for (item in selection) {
-					ser.push(file.getPath());
+					if (item == file)
+						continue;
+					ser.push(item.getPath());
 				}
 				dataTransfer.setData(dragKey, haxe.Json.stringify(ser));
 				return true;
@@ -368,7 +390,7 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				for (file in dataTransfer.files) {
 					var path : String = untyped file.path; //file.path is an extension from nwjs or node
 					path = StringTools.replace(path, "\\", "/");
-					files.push(path);
+					files.push(ide.getRelPath(path));
 				}
 
 				var fileMoveData = dataTransfer.getData(dragKey);
@@ -376,17 +398,45 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 					try {
 						var unser = haxe.Json.parse(fileMoveData);
 						for (file in (unser:Array<String>)) {
-							files.push(file);
+							files.push(ide.getRelPath(file));
 						}
 					} catch (e) {
 						trace("Invalid data " + e);
 					}
 				}
 
+				var roots = getRoots(files);
+				var outerFiles: Array<{from: String, to: String}> = [];
+				for (root in roots) {
+					var movePath = targetPath + "/" + file.split("/").pop();
+					outerFiles.push({from: file, to: movePath});
+				}
+
+				function exec(isUndo: Bool) {
+					if (!isUndo) {
+						for (file in outerFiles) {
+							// File could have been removed by the system in between our undo/redo operations
+							if (sys.FileSystem.exists(ide.getPath(file.from)))
+								FileTree.doRename(file.from, "/" + file.to);
+						}
+					} else {
+						for (file in outerFiles) {
+							// File could have been removed by the system in between our undo/redo operations
+							if (sys.FileSystem.exists(ide.getPath(file.to)))
+								FileTree.doRename(file.to, "/" + file.from);
+						}
+					}
+				}
+
+				undo.change(Custom(exec));
+				exec(false);
+
 				return true;
 			}
 		}
 
+		fancyTree.onContextMenu = contextMenu.bind(false);
+
 		fancyTree.rebuildTree();
 		fancyTree.openItem(root);
 
@@ -535,6 +585,125 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 
 	}
 
+	function createNew( directoryFullPath : String, ext : hide.view.FileTree.ExtensionDesc ) {
+
+		var file = ide.ask(ext.options.createNew + " name:");
+		if( file == null ) return;
+		if( file.indexOf(".") < 0 && ext.extensions != null )
+			file += "." + ext.extensions[0].split(".").shift();
+
+		var newFilePath = directoryFullPath + "/" + file;
+
+		if( sys.FileSystem.exists(newFilePath) ) {
+			ide.error("File '" + file+"' already exists");
+			createNew(directoryFullPath, ext);
+			return;
+		}
+
+		// directory
+		if( ext.component == null ) {
+			sys.FileSystem.createDirectory(newFilePath);
+			return;
+		}
+
+		var view : hide.view.FileView = Type.createEmptyInstance(Type.resolveClass(ext.component));
+		view.ide = ide;
+		view.state = { path : ide.getRelPath(newFilePath)};
+		sys.io.File.saveBytes(newFilePath, view.getDefaultContent());
+
+		ide.openFile(newFilePath);
+	}
+
+	function deleteFiles(fullPaths : Array<String>) {
+		var roots = getRoots(fullPaths);
+		if( sys.FileSystem.isDirectory(fullPath) ) {
+			for( f in sys.FileSystem.readDirectory(fullPath) )
+				onDeleteFile(path + "/" + f);
+			sys.FileSystem.deleteDirectory(fullPath);
+		} else
+			sys.FileSystem.deleteFile(fullPath);
+	}
+
+	function getItemAndSelection(isGallery: Bool) : Array<FileEntry> {
+		var items = [currentFolder];
+		if (!isGallery) {
+			return items.concat(fancyTree.getSelectedItems());
+		}
+		return items;
+	}
+
+	// Deduplicate paths if they are contained in a directory
+	// also present in paths, to simplify bulk operations
+	function getRoots(fullPaths: Array<String>) {
+		var dirs : Array<String> = [];
+
+		for (file in fullPaths) {
+			if(sys.FileSystem.isDirectory(ide.getPath(file))) {
+				dirs.push(file);
+			}
+		}
+
+		// Find the minimum ammount of files that need to be moved
+		var roots: Array<String> = [];
+		for (file in fullPaths) {
+			var isContainedInAnotherDir = false;
+			for (dir2 in dirs) {
+				if (file == dir2)
+					continue;
+				if (StringTools.contains(file, dir2)) {
+					isContainedInAnotherDir = true;
+					continue;
+				}
+			}
+			if (!isContainedInAnotherDir) {
+				roots.push(file);
+			}
+		}
+
+		return roots;
+	}
+
+	function contextMenu(isGallery: Bool, item: FileEntry, event: js.html.MouseEvent) {
+		event.stopPropagation();
+		event.preventDefault();
+
+		if (item == null && !isGallery)
+			item = root;
+		if (item == null)
+			item = currentFolder;
+
+		currentFolder = item;
+		fancyTree.selectItem(currentFolder);
+		queueGalleryRefresh();
+
+		var newMenu = [];
+		for (e in @:privateAccess hide.view.FileTree.EXTENSIONS) {
+			if (e.options.createNew != null) {
+				newMenu.push({
+				label: e.options.createNew,
+				click : createNew.bind(currentFolder.getPath(), e),
+				icon : e.options.icon,
+				});
+			}
+		}
+
+		var options : Array<hide.comp.ContextMenu.MenuItem> = [];
+		options.push({
+			label: "New ...",
+			menu: newMenu,
+		});
+
+		options.push({
+			isSeparator: true;
+		});
+
+		options.push({
+			lable: "Delete", click: deleteFile.bind()
+		})
+
+		hide.comp.ContextMenu.createFromEvent(event, options);
+	}
+
 	function generateFilters() {
 		for (ext => desc in @:privateAccess FileTree.EXTENSIONS) {
 			var name = desc?.options.name;

+ 9 - 9
hide/view/FileTree.hx

@@ -293,11 +293,11 @@ class FileTree extends FileView {
 		return true;
 	}
 
-	function doRename(path:String, name:String) {
-		var isDir = sys.FileSystem.isDirectory(ide.getPath(path));
-		if( isDir ) ide.fileWatcher.pause();
+	public static function doRename(path:String, name:String) {
+		var isDir = sys.FileSystem.isDirectory(hide.Ide.inst.getPath(path));
+		if( isDir ) hide.Ide.inst.fileWatcher.pause();
 		var ret = onRenameRec(path, name);
-		if( isDir ) ide.fileWatcher.resume();
+		if( isDir ) hide.Ide.inst.fileWatcher.resume();
 		return ret;
 	}
 
@@ -311,8 +311,8 @@ class FileTree extends FileView {
 		});
 	}
 
-	function onRenameRec(path:String, name:String) {
-
+	public static function onRenameRec(path:String, name:String) {
+		var ide = hide.Ide.inst;
 		var parts = path.split("/");
 		parts.pop();
 		for( n in name.split("/") ) {
@@ -485,7 +485,7 @@ class FileTree extends FileView {
 		return true;
 	}
 
-	function replacePathInFiles(oldPath: String, newPath: String, isDir: Bool = false) {
+	static function replacePathInFiles(oldPath: String, newPath: String, isDir: Bool = false) {
 		function filter(ctx: hide.Ide.FilterPathContext) {
 			var p = ctx.valueCurrent;
 			if( p == null )
@@ -510,7 +510,7 @@ class FileTree extends FileView {
 			}
 		}
 
-		ide.filterPaths(filter);
+		hide.Ide.inst.filterPaths(filter);
 	}
 
 	function onAllowMove(e: String, to : String) {
@@ -518,7 +518,7 @@ class FileTree extends FileView {
 		return sys.FileSystem.isDirectory(destAbsPath);
 	}
 
-	function doMove(e : String, to : String, index : Int) {
+	static function doMove(e : String, to : String, index : Int) {
 		var dest = "/" + to + "/" + e.split("/").pop();
 		doRename(e, dest);
 	}