Преглед изворни кода

[fileBrowser] Better context menu handling

Clément Espeute пре 2 месеци
родитељ
комит
f92f907249
4 измењених фајлова са 103 додато и 46 уклоњено
  1. 11 3
      hide/comp/FancyTree.hx
  2. 60 12
      hide/tools/FileManager.hx
  3. 2 3
      hide/tools/ThumbnailGenerator.hx
  4. 30 28
      hide/view/FileBrowser.hx

+ 11 - 3
hide/comp/FancyTree.hx

@@ -115,7 +115,6 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 		}
  	}
 
-
 	/**
 		To customise the icon of an element
 	**/
@@ -499,6 +498,14 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 		refreshQueued = false;
 	}
 
+	public function invalidateChildren(item: TreeItem) {
+		var data = itemMap.get(cast item);
+		if (data == null)
+			return;
+		data.children = null;
+		queueRefresh(Search);
+	}
+
 	public function filterRec(children: Array<TreeItemData<TreeItem>>) : Bool {
 		var anyVisible = false;
 		for (child in children) {
@@ -549,8 +556,6 @@ 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);
@@ -558,12 +563,15 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			});
 
 			var closure = dataClickHandler.bind(data);
+			var ctxMenuClosure = contextMenuHandler.bind(data.item);
 
 			var icon = element.querySelector(".header-icon");
 			icon.onclick = closure;
+			icon.oncontextmenu = ctxMenuClosure;
 
 			var name = element.querySelector("fancy-tree-name");
 			name.onclick = closure;
+			name.oncontextmenu = ctxMenuClosure;
 
 			data.element = element;
 

+ 60 - 12
hide/tools/FileManager.hx

@@ -29,6 +29,7 @@ class FileEntry {
 	public var kind: FileKind;
 	public var parent: FileEntry;
 	public var iconPath: String;
+	public var disposed: Bool = false;
 
 	var registeredWatcher : hide.tools.FileWatcher.FileWatchEvent = null;
 
@@ -40,12 +41,17 @@ class FileEntry {
 		watch();
 	}
 
+	public final function toString() : String{
+		return name;
+	}
+
 	public function dispose() {
 		if (children != null) {
 			for (child in children) {
 				child.dispose();
 			}
 		}
+		disposed = true;
 		children = null;
 		if (registeredWatcher != null) {
 			hide.Ide.inst.fileWatcher.unregister(this.getPath(), registeredWatcher.fun);
@@ -53,7 +59,7 @@ class FileEntry {
 		}
 	}
 
-	function refreshChildren(rec: Bool) {
+	function refreshChildren() {
 		if (kind != Dir)
 			return;
 		var fullPath = getPath();
@@ -77,9 +83,9 @@ class FileEntry {
 					oldChildren.remove(path);
 				} else {
 					var info = js.node.Fs.statSync(fullPath + "/" + path);
-					children.push(
-						new FileEntry(path, this, info.isDirectory() ? Dir : File)
-					);
+					var newEntry = new FileEntry(path, this, info.isDirectory() ? Dir : File);
+					newEntry.refreshChildren();
+					children.push(newEntry);
 				}
 			}
 		}
@@ -89,12 +95,6 @@ class FileEntry {
 		}
 
 		children.sort(compareFile);
-
-		if (rec) {
-			for (child in children) {
-				child.refreshChildren(rec);
-			}
-		}
 	}
 
 	function watch() {
@@ -197,6 +197,51 @@ class FileManager {
 		}
 	}
 
+	public function deleteFiles(files : Array<FileEntry>) {
+		//trace(fullPaths);
+		var roots = getRoots(files);
+		for (file in roots) {
+			if( file.kind == Dir ) {
+				file.dispose(); // kill watchers
+				untyped js.node.Fs.rmSync(file.getPath(), {force: true, recursive: true});
+			} else {
+				file.dispose(); // kill watchers
+				untyped js.node.Fs.rmSync(file.getPath(), {force: true, recursive: false});
+			}
+		}
+	}
+
+	// Deduplicate paths if they are contained in a directory
+	// also present in paths, to simplify bulk operations
+	public function getRoots(files: Array<FileEntry>) : Array<FileEntry> {
+		var dirs : Array<FileEntry> = [];
+
+		for (file in files) {
+			if(file.kind == Dir) {
+				dirs.push(file);
+			}
+		}
+
+		// Find the minimum ammount of files that need to be moved
+		var roots: Array<FileEntry> = [];
+		for (file in files) {
+			var isContainedInAnotherDir = false;
+			for (dir2 in dirs) {
+				if (file == dir2)
+					continue;
+				if (StringTools.contains(file.getPath(), dir2.getPath())) {
+					isContainedInAnotherDir = true;
+					continue;
+				}
+			}
+			if (!isContainedInAnotherDir) {
+				roots.push(file);
+			}
+		}
+
+		return roots;
+	}
+
 	function setupServer() {
 		if (serverSocket != null)
 			throw "Server already exists";
@@ -266,14 +311,17 @@ class FileManager {
 
 	function initFileSystem() {
 		fileEntryRefreshDelay = new Delayer((entry: FileEntry) -> {
-			entry.refreshChildren(false);
+			entry.refreshChildren();
 		});
 
 		fileRoot = new FileEntry("res", null, Dir);
-		fileRoot.refreshChildren(true);
+		fileRoot.refreshChildren();
 	}
 
 	function fileChangeInternal(entry: FileEntry) {
+		// invalidate thumbnail
+		entry.iconPath = null;
+
 		if (!js.node.Fs.existsSync(entry.getPath()) && entry.parent != null) {
 			fileEntryRefreshDelay.queue(entry.parent);
 			return;

+ 2 - 3
hide/tools/ThumbnailGenerator.hx

@@ -222,11 +222,10 @@ class ThumbnailGenerator {
 			model.make(ctx);
 		} else if (ext == "prefab" || ext == "l3d" || ext == "fx") {
 			try {
-				var ref = new hrt.prefab.Reference(null, null);
 				var cut = StringTools.replace(toRender.path, hide.Ide.inst.resourceDir + "/", "");
-				ref.source = cut;
+				var prefab = hxd.res.Loader.currentInstance.load(cut).toPrefab().loadBypassCache();
 
-				var prefab = ref.make(ctx);
+				var prefab = prefab.make(ctx);
 
 				if (ext == "fx") {
 					var fx = prefab.find(hrt.prefab.fx.FX, true, false);

+ 30 - 28
hide/view/FileBrowser.hx

@@ -156,7 +156,7 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 	}
 
 	function onFileChange(file: FileEntry) {
-		fancyTree.refreshItem(file);
+		fancyTree.invalidateChildren(file);
 		queueGalleryRefresh();
 	}
 
@@ -226,6 +226,8 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				return [root];
 			if (file.kind == File)
 				return null;
+			if (file.disposed)
+				throw "disposed file";
 			if (file.children == null)
 				throw "null children";
 			return file.children.filter((file) -> file.kind == Dir);
@@ -510,31 +512,15 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 		ide.openFile(newFilePath);
 	}
 
-	function deleteFiles(fullPaths : Array<String>) {
-		//trace(fullPaths);
-		var roots = getRoots(fullPaths);
-		for (fullPath in roots) {
-			if( sys.FileSystem.isDirectory(fullPath) ) {
-				var filesInDir = [];
-				for (f in sys.FileSystem.readDirectory(fullPath)) {
-					trace("files in dir :", f);
-					filesInDir.push(fullPath + "/" + f);
-				}
-				if (filesInDir.length > 0)
-					deleteFiles(filesInDir);
-				sys.FileSystem.deleteDirectory(fullPath);
-			} else
-				sys.FileSystem.deleteFile(fullPath);
-		}
-	}
-
 	function getItemAndSelection(baseItem: FileEntry, isGallery: Bool) : Array<FileEntry> {
 		var items = [];
 		if (baseItem != null) {
 			items.push(baseItem);
 		}
 		if (!isGallery) {
-			return items.concat(fancyTree.getSelectedItems());
+			for (item in fancyTree.getSelectedItems()) {
+				hide.tools.Extensions.ArrayExtensions.pushUnique(items, item);
+			}
 		}
 		return items;
 	}
@@ -574,10 +560,17 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 		event.stopPropagation();
 		event.preventDefault();
 
+		// if the user clicked on the background of the file tree, don't display anything
 		if (item == null && !isGallery)
-			item = root;
-		if (item == null)
+			return;
+
+		// if the user selected the "current" folder in the gallery
+		// prevent move/delete ... operations on it to avoid confusion and wrong operations
+		var implicitFolder = false;
+		if (item == null) {
+			implicitFolder = true;
 			item = currentFolder;
+		}
 
 		/*currentFolder = item;
 		fancyTree.selectItem(currentFolder);
@@ -601,17 +594,26 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				label: "New ...",
 				menu: newMenu,
 			});
+		}
+
+		if (!implicitFolder) {
+			if (options[options.length-1] != null && !options[options.length-1].isSeparator) {
+				options.push({
+					isSeparator: true,
+					menu: newMenu,
+				});
+			}
 
 			options.push({
-				isSeparator: true,
+				label: "Delete", click: () -> {
+					var selection = getItemAndSelection(item, isGallery);
+					var roots = FileManager.inst.getRoots(selection);
+					if(ide.confirm("Confirm deleting files : " + [for (r in roots) r.getRelPath()].join("\n") + '\n(Cannot be undone)'))
+						FileManager.inst.deleteFiles(getItemAndSelection(item, isGallery));
+				}
 			});
 		}
 
-		options.push({
-			label: "Delete", click: () -> {
-				deleteFiles([for (file in getItemAndSelection(item, isGallery)) file.getPath()]);
-			}
-		});
 
 		hide.comp.ContextMenu.createFromEvent(event, options);
 	}