Browse Source

[filebrowser] Support most of all old filebrowser operations

Clément Espeute 2 months ago
parent
commit
43078476dd

+ 3 - 3
bin/style.css

@@ -5397,9 +5397,9 @@ fancy-gallery fancy-scroll fancy-item-container fancy-item.details fancy-image {
 }
 }
 fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name {
 fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name {
   position: absolute;
   position: absolute;
-  bottom: 0px;
-  left: 0px;
-  right: 0px;
+  bottom: 2px;
+  left: 2px;
+  right: 2px;
   height: 32px;
   height: 32px;
   display: block;
   display: block;
   text-align: center;
   text-align: center;

+ 3 - 4
bin/style.less

@@ -6500,9 +6500,9 @@ fancy-gallery {
 
 
 				fancy-name {
 				fancy-name {
 					position: absolute;
 					position: absolute;
-					bottom: 0px;
-					left: 0px;
-					right: 0px;
+					bottom: 2px;
+					left: 2px;
+					right: 2px;
 					height: 32px;
 					height: 32px;
 					display: block;
 					display: block;
 					text-align: center;
 					text-align: center;
@@ -6510,7 +6510,6 @@ fancy-gallery {
 					word-break: break-all;
 					word-break: break-all;
 					text-overflow: ellipsis;
 					text-overflow: ellipsis;
 					overflow: hidden;
 					overflow: hidden;
-
 				}
 				}
 
 
 				&.details fancy-name {
 				&.details fancy-name {

+ 2 - 1
hide/comp/ContentEditable.hx

@@ -19,7 +19,8 @@ class ContentEditable extends Component {
 
 
 		html.onfocus = function() {
 		html.onfocus = function() {
 			var range = js.Browser.document.createRange();
 			var range = js.Browser.document.createRange();
-			range.selectNodeContents(html);
+			range.selectNodeContents(element.get(0));
+			trace(element.get(0), range);
 			var sel = js.Browser.window.getSelection();
 			var sel = js.Browser.window.getSelection();
 			sel.removeAllRanges();
 			sel.removeAllRanges();
 			sel.addRange(range);
 			sel.addRange(range);

+ 39 - 2
hide/comp/FancyGallery.hx

@@ -43,6 +43,11 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 			</fancy-scroll>
 			</fancy-scroll>
 		");
 		");
 
 
+		var htmlElem = el.get(0);
+		htmlElem.tabIndex = -1;
+
+		htmlElem.onkeydown = inputHandler;
+
 		var resizeObserver = new hide.comp.ResizeObserver((_, _) -> {
 		var resizeObserver = new hide.comp.ResizeObserver((_, _) -> {
 			queueRefresh();
 			queueRefresh();
 		});
 		});
@@ -112,6 +117,13 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 	public dynamic function onDoubleClick(item: GalleryItem) : Void {
 	public dynamic function onDoubleClick(item: GalleryItem) : Void {
 	}
 	}
 
 
+	/**
+		Custom keyboard handler, register your shortcuts here
+	**/
+	public dynamic function onKeyPress(event: js.html.KeyboardEvent) : Bool {
+		return false;
+	}
+
 	/**
 	/**
 		Called when an item becomes visible on screen due to scrolling or other things.
 		Called when an item becomes visible on screen due to scrolling or other things.
 	**/
 	**/
@@ -155,14 +167,32 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 		// onDrop: (target: TreeItem, where: DropOperation, dataTransfer: js.html.DataTransfer) -> Void
 		// onDrop: (target: TreeItem, where: DropOperation, dataTransfer: js.html.DataTransfer) -> Void
 	} = null;
 	} = null;
 
 
-
-
 	public function rebuild() {
 	public function rebuild() {
 		queueRefresh(Items);
 		queueRefresh(Items);
 		queueRefresh(Search);
 		queueRefresh(Search);
 		queueRefresh(RegenHeader);
 		queueRefresh(RegenHeader);
 	}
 	}
 
 
+	public function rename(item: GalleryItem, onFinished : (newName:String) -> Void) {
+		var data = itemMap.get(cast item);
+		var name = data.element.querySelector("fancy-name");
+		name.contentEditable = "plaintext-only";
+		var editable = new ContentEditable(null, new Element(data.element.querySelector("fancy-name")));
+
+		editable.onCancel = () -> {
+			queueRefresh(RegenHeader);
+			element.focus();
+		}
+
+		editable.onChange = (newValue) -> {
+			onFinished(name.textContent);
+			queueRefresh(RegenHeader);
+			element.focus();
+		}
+
+		editable.element.focus();
+	}
+
 	/**
 	/**
 		Never call this directly
 		Never call this directly
 	**/
 	**/
@@ -331,4 +361,11 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 			currentData.push(data);
 			currentData.push(data);
 		}
 		}
 	}
 	}
+
+	function inputHandler(event: js.html.KeyboardEvent) {
+		if (onKeyPress(event)) {
+			event.stopPropagation();
+			event.preventDefault();
+		}
+	}
 }
 }

+ 38 - 10
hide/comp/FancyTree.hx

@@ -105,9 +105,11 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 
 
 		scroll.onscroll = (e) -> queueRefresh();
 		scroll.onscroll = (e) -> queueRefresh();
 
 
-		fancyTree.onblur = (e) -> {
-			currentVisible = false;
-			currentItem = null;
+		fancyTree.onblur = (e: js.html.FocusEvent) -> {
+			if (!fancyTree.contains(cast e.relatedTarget)) {
+				currentVisible = false;
+				currentItem = null;
+			}
 		}
 		}
 
 
 		fancyTree.onclick = (e) -> {
 		fancyTree.onclick = (e) -> {
@@ -188,8 +190,7 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 	/**
 	/**
 		Called when the user renamed the item via F2 / Context menu
 		Called when the user renamed the item via F2 / Context menu
 	**/
 	**/
-	public dynamic function onNameChange(item: TreeItem, newName: String) : Void {
-	}
+	public var onNameChange : (item: TreeItem, newName: String) -> Void;
 
 
 	/**
 	/**
 		Called for each of your items in the tree. for the root elements, get called with null as a parameter
 		Called for each of your items in the tree. for the root elements, get called with null as a parameter
@@ -305,6 +306,32 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 		}
 		}
 	}
 	}
 
 
+	public function rename(item: TreeItem) : Void {
+		if (onNameChange == null)
+			return;
+		var data = itemMap.get(cast item);
+		if (data == null)
+			return;
+
+		var name = data.element.querySelector("fancy-tree-name");
+		//name.innerText = data.name;
+		name.contentEditable = "plaintext-only";
+		var edit = new ContentEditable(null, new Element(name));
+
+		edit.onChange = (newValue) -> {
+			onNameChange(item, name.textContent);
+			queueRefresh(RegenHeader);
+			element.focus();
+		}
+
+		edit.onCancel = () -> {
+			queueRefresh(RegenHeader);
+			element.focus();
+		}
+
+		edit.element.focus();
+	}
+
 	function inputHandler(e: js.html.KeyboardEvent) {
 	function inputHandler(e: js.html.KeyboardEvent) {
 		if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) {
 		if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) {
 			e.stopPropagation();
 			e.stopPropagation();
@@ -314,12 +341,13 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			searchBar.focus();
 			searchBar.focus();
 		}
 		}
 
 
-		// if (hide.ui.Keys.matchJsEvent("rename", e, ide.currentConfig) && selection.iterator().hasNext()) {
-		// 	e.stopPropagation();
-		// 	e.preventDefault();
+		if (hide.ui.Keys.matchJsEvent("rename", e, ide.currentConfig) && selection.iterator().hasNext()) {
+			e.stopPropagation();
+			e.preventDefault();
 
 
-		// 	beginRename(cast selection.keyValueIterator().next().key);
-		// }
+			if (currentItem != null)
+				rename(currentItem.item);
+		}
 
 
 		if (e.key == "Escape") {
 		if (e.key == "Escape") {
 			if (searchBarClosable.isOpen()) {
 			if (searchBarClosable.isOpen()) {

+ 28 - 1
hide/tools/FileManager.hx

@@ -124,7 +124,7 @@ class FileEntry {
 			}
 			}
 			return 1;
 			return 1;
 		}
 		}
-		return Reflect.compare(a.name, b.name);
+		return Reflect.compare(a.name.toLowerCase(), b.name.toLowerCase());
 	}
 	}
 }
 }
 
 
@@ -334,6 +334,33 @@ class FileManager {
 		}
 		}
 	}
 	}
 
 
+	public function cloneFile(entry: FileEntry) {
+		var sourcePath = entry.getPath();
+		var nameNewFile = hide.Ide.inst.ask("New filename:", new haxe.io.Path(sourcePath).file);
+		if (nameNewFile == null || nameNewFile.length == 0) {
+			return false;
+		}
+
+		var targetPath = new haxe.io.Path(sourcePath).dir + "/" + nameNewFile;
+		if ( sys.FileSystem.exists(targetPath) ) {
+			throw "File already exists";
+		}
+
+		if( sys.FileSystem.isDirectory(sourcePath) ) {
+			sys.FileSystem.createDirectory(targetPath + "/");
+			for( f in sys.FileSystem.readDirectory(sourcePath) ) {
+				sys.io.File.saveBytes(targetPath + "/" + f, sys.io.File.getBytes(sourcePath + "/" + f));
+			}
+		} else {
+			if (targetPath.indexOf(".") == -1) {
+				var oldExt = sourcePath.split(".").pop();
+				targetPath += "." + oldExt;
+			}
+			sys.io.File.saveBytes(targetPath, sys.io.File.getBytes(sourcePath));
+		}
+		return true;
+	}
+
 
 
 	function processThumbnailGeneratorMessage(message: String) {
 	function processThumbnailGeneratorMessage(message: String) {
 		try {
 		try {

+ 134 - 30
hide/view/FileBrowser.hx

@@ -319,9 +319,7 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 			return null;
 			return null;
 		}
 		}
 
 
-		fancyTree.onNameChange = (item: FileEntry, newName: String) -> {
-			item.name = newName;
-		}
+		fancyTree.onNameChange = renameHandler;
 
 
 		fancyTree.dragAndDropInterface =
 		fancyTree.dragAndDropInterface =
 		{
 		{
@@ -362,7 +360,8 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				for (file in dataTransfer.files) {
 				for (file in dataTransfer.files) {
 					var path : String = untyped file.path; //file.path is an extension from nwjs or node
 					var path : String = untyped file.path; //file.path is an extension from nwjs or node
 					path = StringTools.replace(path, "\\", "/");
 					path = StringTools.replace(path, "\\", "/");
-					files.push(ide.getRelPath(path));
+					var rel = ide.getRelPath(path);
+					files.push(rel);
 				}
 				}
 
 
 				var fileMoveData = dataTransfer.getData(dragKey);
 				var fileMoveData = dataTransfer.getData(dragKey);
@@ -377,32 +376,7 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 					}
 					}
 				}
 				}
 
 
-				var roots = getRoots(files);
-				var outerFiles: Array<{from: String, to: String}> = [];
-				var targetPath = target.getPath();
-				for (root in roots) {
-					var movePath = targetPath + "/" + root.split("/").pop();
-					outerFiles.push({from: root, 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);
+				moveFiles(target.getRelPath(), files);
 
 
 				return true;
 				return true;
 			}
 			}
@@ -567,6 +541,66 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 		layout = state.savedLayout ?? Horizontal;
 		layout = state.savedLayout ?? Horizontal;
 	}
 	}
 
 
+	function renameHandler(item: FileEntry, newName: String) {
+		if (newName.indexOf(".") == -1) {
+			newName += "." + item.name.split(".").pop();
+		}
+
+		var newPath = item.getRelPath().split("/");
+		newPath.pop();
+		newPath.push(newName);
+		renameFile(item.getRelPath(), newPath.join("/"));
+	}
+
+	/**
+		Path is relative to res folder
+	**/
+	function moveFiles(targetFolder: String, files: Array<String>) {
+		var roots = getRoots(files);
+		var outerFiles: Array<{from: String, to: String}> = [];
+		for (root in roots) {
+			var movePath = targetFolder + "/" + root.split("/").pop();
+			outerFiles.push({from: root, to: movePath});
+		}
+
+		var exec = execMoveFiles.bind(outerFiles);
+
+		undo.change(Custom(exec));
+		exec(false);
+	}
+
+	static function execMoveFiles(operations: Array<{from: String, to: String}>, isUndo: Bool) : Void {
+		if (!isUndo) {
+			for (file in operations) {
+				// File could have been removed by the system in between our undo/redo operations
+				if (sys.FileSystem.exists(hide.Ide.inst.getPath(file.from))) {
+					try {
+						FileTree.doRename(file.from, "/" + file.to);
+					} catch (e) {
+						hide.Ide.inst.quickError('move file ${file.from} -> ${file.to} failed : $e');
+					}
+				}
+			}
+		} else {
+			for (file in operations) {
+				// File could have been removed by the system in between our undo/redo operations
+				if (sys.FileSystem.exists(hide.Ide.inst.getPath(file.to))) {
+					try {
+						FileTree.doRename(file.to, "/" + file.from);
+					} catch (e) {
+						hide.Ide.inst.quickError('move file ${file.from} -> ${file.to} failed : $e');
+					}
+				}
+			}
+		}
+	}
+
+	function renameFile(oldPath: String, newPath: String) {
+		var exec = execMoveFiles.bind([{from: oldPath, to: newPath}]);
+		undo.change(Custom(exec));
+		exec(false);
+	}
+
 	override function destroy() {
 	override function destroy() {
 		super.destroy();
 		super.destroy();
 		FileManager.inst.onFileChangeHandlers.remove(onFileChange);
 		FileManager.inst.onFileChangeHandlers.remove(onFileChange);
@@ -693,6 +727,55 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 				});
 				});
 			}
 			}
 
 
+			options.push({
+				label: "Copy Path",
+				click: () -> ide.setClipboard(item.getRelPath())
+			});
+
+			options.push({
+				label: "Copy Absolute Path",
+				click: () -> ide.setClipboard(item.getPath())
+			});
+
+			options.push({
+				label : "Open in Explorer",
+				click : () -> Ide.showFileInExplorer(item.getPath())
+			});
+
+			options.push({ label : "Find References", click : onFindPathRef.bind(item.getRelPath())});
+
+			options.push({
+				isSeparator: true,
+				menu: newMenu,
+			});
+
+			options.push({
+				label: "Clone", click: () -> {
+					hide.tools.FileManager.inst.cloneFile(item);
+				}
+			});
+
+			options.push({
+				label: "Rename", click: () -> {
+					if (!isGallery) {
+						fancyTree.rename(item);
+					} else {
+						fancyGallery.rename(item, (newName:String) -> renameHandler(item, newName));
+					}
+				}, keys: config.get("key.rename"),
+			});
+
+			options.push({
+				label: "Move", click: () -> {
+					ide.chooseDirectory(function(dir) {
+						var selection = getItemAndSelection(item, isGallery);
+						var roots = FileManager.inst.getRoots(selection);
+						moveFiles(dir, [for (file in roots) file.getRelPath()]);
+					});
+				}
+			});
+
+
 			options.push({
 			options.push({
 				label: "Delete", click: () -> {
 				label: "Delete", click: () -> {
 					var selection = getItemAndSelection(item, isGallery);
 					var selection = getItemAndSelection(item, isGallery);
@@ -701,12 +784,33 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 						FileManager.inst.deleteFiles(getItemAndSelection(item, isGallery));
 						FileManager.inst.deleteFiles(getItemAndSelection(item, isGallery));
 				}
 				}
 			});
 			});
+
+			options.push({ label: "Replace Refs With", click : function() {
+				ide.chooseFile(["*"], (newPath: String) -> {
+					var selection = [for (file in getItemAndSelection(item, isGallery)) file.getRelPath()];
+					if(ide.confirm('Replace all refs of $selection with $newPath ? This action can not be undone')) {
+						for (oldPath in selection) {
+							FileTree.replacePathInFiles(oldPath, newPath, false);
+						}
+						ide.message("Done");
+					}
+				});
+			}});
+
 		}
 		}
 
 
 
 
 		hide.comp.ContextMenu.createFromEvent(event, options);
 		hide.comp.ContextMenu.createFromEvent(event, options);
 	}
 	}
 
 
+	function onFindPathRef(path: String) {
+		var refs = ide.search(path, ["hx", "prefab", "fx", "cdb", "json", "props", "ddt"], ["bin"]);
+		ide.open("hide.view.RefViewer", null, null, function(view) {
+			var refViewer : hide.view.RefViewer = cast view;
+			refViewer.showRefs(refs, 'Number of references to "$path"', path);
+		});
+	}
+
 	function generateFilters() {
 	function generateFilters() {
 		for (ext => desc in @:privateAccess FileTree.EXTENSIONS) {
 		for (ext => desc in @:privateAccess FileTree.EXTENSIONS) {
 			var name = desc?.options.name;
 			var name = desc?.options.name;

+ 2 - 2
hide/view/FileTree.hx

@@ -44,7 +44,7 @@ class FileTree extends FileView {
 		return false;
 		return false;
 	}
 	}
 
 
-	static function getExtension( file : String ) {
+	public static function getExtension( file : String ) {
 		var ext = new haxe.io.Path(file).ext;
 		var ext = new haxe.io.Path(file).ext;
 		if( ext == null ) return null;
 		if( ext == null ) return null;
 		ext = ext.toLowerCase();
 		ext = ext.toLowerCase();
@@ -485,7 +485,7 @@ class FileTree extends FileView {
 		return true;
 		return true;
 	}
 	}
 
 
-	static function replacePathInFiles(oldPath: String, newPath: String, isDir: Bool = false) {
+	public static function replacePathInFiles(oldPath: String, newPath: String, isDir: Bool = false) {
 		function filter(ctx: hide.Ide.FilterPathContext) {
 		function filter(ctx: hide.Ide.FilterPathContext) {
 			var p = ctx.valueCurrent;
 			var p = ctx.valueCurrent;
 			if( p == null )
 			if( p == null )

+ 11 - 0
hide/view/Gym.hx

@@ -140,6 +140,17 @@ class Gym extends hide.ui.View<{}> {
 						<span class="ico ico-paint-brush"></span>
 						<span class="ico ico-paint-brush"></span>
 					</fancy-button>
 					</fancy-button>
 				</fancy-toolbar>'));
 				</fancy-toolbar>'));
+
+			demo.append(new Element("<h1>Contenteditable</h1>"));
+			var ce = new hide.comp.ContentEditable(demo);
+			ce.element.text("Edit me !");
+
+			var ce2 = new Element("<fancy-name>Edit me too !</fancy-name>");
+			demo.append(ce2);
+			ce2.get(0).contentEditable = "true";
+			var ce3 = new hide.comp.ContentEditable(null,ce2);
+			ce3.onChange = (v) -> trace(v);
+			demo.append(new Element("<fancy-button>Focus</fancy-button>").click((_) -> ce.element.focus()));
 		}
 		}
 
 
 		{
 		{