[studio] Add ability to move directories
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build / build (push) Successful in 3m29s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build / build (push) Successful in 3m29s
				
			This commit is contained in:
		| @@ -238,6 +238,9 @@ class AssetManager { | ||||
| 				} | ||||
|  | ||||
| 				ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept final { | ||||
| 					if (!m_cache.contains(oldId)) { | ||||
| 						return {}; | ||||
| 					} | ||||
| 					auto &o = m_cache[oldId]; | ||||
| 					auto &n = m_cache[newId]; | ||||
| 					n = std::move(o); | ||||
| @@ -246,11 +249,14 @@ class AssetManager { | ||||
| 				} | ||||
|  | ||||
| 				void gc() noexcept final { | ||||
| 					for (auto const&ack : m_cache.keys()) { | ||||
| 					for (size_t i = 0; i < m_cache.keys().size();) { | ||||
| 						auto const &ack = m_cache.keys()[i]; | ||||
| 						auto &ac = m_cache[ack]; | ||||
| 						if (!ac->references()) { | ||||
| 							m_cache.erase(ack); | ||||
| 							continue; | ||||
| 						} | ||||
| 						++i; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| @@ -309,6 +315,9 @@ class AssetManager { | ||||
| 			if (m_fileUpdated.contains(newId)) { | ||||
| 				return ox::Error{1, "new asset ID already has an entry"}; | ||||
| 			} | ||||
| 			if (!m_fileUpdated.contains(oldId)) { | ||||
| 				return {}; | ||||
| 			} | ||||
| 			auto &o = m_fileUpdated[oldId]; | ||||
| 			auto &n = m_fileUpdated[newId]; | ||||
| 			n = std::move(o); | ||||
|   | ||||
| @@ -30,6 +30,10 @@ void ProjectExplorer::fileMoved(ox::StringViewCR src, ox::StringViewCR dst) cons | ||||
| 	moveItem.emit(src, dst); | ||||
| } | ||||
|  | ||||
| void ProjectExplorer::dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept { | ||||
| 	moveDir.emit(src, dst); | ||||
| } | ||||
|  | ||||
| void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept { | ||||
| 	if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { | ||||
| 		if (ImGui::MenuItem("Delete")) { | ||||
|   | ||||
| @@ -20,6 +20,7 @@ class ProjectExplorer final: public FileExplorer { | ||||
| 		ox::Signal<ox::Error(ox::StringViewCR)> addDir; | ||||
| 		ox::Signal<ox::Error(ox::StringViewCR)> deleteItem; | ||||
| 		ox::Signal<ox::Error(ox::StringViewCR)> renameItem; | ||||
| 		ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveDir; | ||||
| 		ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveItem; | ||||
|  | ||||
| 		explicit ProjectExplorer(keel::Context &kctx) noexcept; | ||||
| @@ -33,6 +34,8 @@ class ProjectExplorer final: public FileExplorer { | ||||
|  | ||||
| 		void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; | ||||
|  | ||||
| 		void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; | ||||
|  | ||||
| 		void fileContextMenu(ox::StringViewCR path) const noexcept override; | ||||
|  | ||||
| 		void dirContextMenu(ox::StringViewCR path) const noexcept override; | ||||
|   | ||||
| @@ -59,6 +59,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce | ||||
| 	m_projectExplorer.addItem.connect(this, &StudioUI::addFile); | ||||
| 	m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); | ||||
| 	m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile); | ||||
| 	m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove); | ||||
| 	m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove); | ||||
| 	m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove); | ||||
| 	m_newProject.finished.connect(this, &StudioUI::createOpenProject); | ||||
| @@ -486,6 +487,11 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error StudioUI::queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept { | ||||
| 	m_queuedDirMoves.emplace_back(std::move(src), std::move(dst)); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept { | ||||
| 	m_queuedMoves.emplace_back(std::move(src), std::move(dst)); | ||||
| 	return {}; | ||||
| @@ -496,6 +502,10 @@ void StudioUI::procFileMoves() noexcept { | ||||
| 		oxLogError(m_project->moveItem(m.a, m.b)); | ||||
| 	} | ||||
| 	m_queuedMoves.clear(); | ||||
| 	for (auto const &m : m_queuedDirMoves) { | ||||
| 		oxLogError(m_project->moveDir(m.a, m.b)); | ||||
| 	} | ||||
| 	m_queuedDirMoves.clear(); | ||||
| } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -40,7 +40,8 @@ class StudioUI: public ox::SignalHandler { | ||||
| 		BaseEditor *m_activeEditorOnLastDraw = nullptr; | ||||
| 		BaseEditor *m_activeEditor = nullptr; | ||||
| 		BaseEditor *m_activeEditorUpdatePending = nullptr; | ||||
| 		ox::Vector<ox::Pair<ox::String, ox::String>> m_queuedMoves; | ||||
| 		ox::Vector<ox::Pair<ox::String>> m_queuedMoves; | ||||
| 		ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves; | ||||
| 		NewMenu m_newMenu{keelCtx(m_tctx)}; | ||||
| 		DeleteConfirmation m_deleteConfirmation; | ||||
| 		NewDir m_newDirDialog; | ||||
| @@ -113,6 +114,8 @@ class StudioUI: public ox::SignalHandler { | ||||
|  | ||||
| 		ox::Error closeFile(ox::StringViewCR path) noexcept; | ||||
|  | ||||
| 		ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept; | ||||
|  | ||||
| 		ox::Error queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept; | ||||
|  | ||||
| 		void procFileMoves() noexcept; | ||||
|   | ||||
| @@ -13,10 +13,12 @@ struct FileRef { | ||||
| 	static constexpr auto TypeName = "net.drinkingtea.studio.FileRef"; | ||||
| 	static constexpr auto TypeVersion = 1; | ||||
| 	ox::String path; | ||||
| 	bool isDir{}; | ||||
| }; | ||||
|  | ||||
| OX_MODEL_BEGIN(FileRef) | ||||
| 	OX_MODEL_FIELD(path) | ||||
| 	OX_MODEL_FIELD(isDir) | ||||
| OX_MODEL_END() | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -47,6 +47,8 @@ class FileExplorer: public ox::SignalHandler { | ||||
|  | ||||
| 		virtual void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; | ||||
|  | ||||
| 		virtual void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; | ||||
|  | ||||
| 		void drawFileContextMenu(ox::CStringViewCR path) const noexcept; | ||||
|  | ||||
| 		void drawDirContextMenu(ox::CStringViewCR path) const noexcept; | ||||
|   | ||||
| @@ -94,6 +94,8 @@ class Project: public ox::SignalHandler { | ||||
|  | ||||
| 		ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept; | ||||
|  | ||||
| 		ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept; | ||||
|  | ||||
| 		ox::Error deleteItem(ox::StringViewCR path) noexcept; | ||||
|  | ||||
| 		[[nodiscard]] | ||||
|   | ||||
| @@ -43,6 +43,8 @@ void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {} | ||||
|  | ||||
| void FileExplorer::fileMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} | ||||
|  | ||||
| void FileExplorer::dirMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} | ||||
|  | ||||
| void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept { | ||||
| 	ig::IDStackItem const idStackItem{path}; | ||||
| 	fileContextMenu(path); | ||||
| @@ -93,11 +95,21 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept { | ||||
| 		} | ||||
| 		ig::IDStackItem const idStackItem{m_name}; | ||||
| 		m_explorer.drawDirContextMenu(m_fullPath); | ||||
| 		if (m_explorer.fileDraggable()) { | ||||
| 			std::ignore = ig::dragDropSource([this] { | ||||
| 				ImGui::Text("%s", m_name.c_str()); | ||||
| 				return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}, true}); | ||||
| 			}); | ||||
| 		} | ||||
| 		if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { | ||||
| 			auto const [ref, err] = ig::getDragDropPayload<FileRef>("FileRef"); | ||||
| 			if (!err) { | ||||
| 				auto const name = substr(ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1); | ||||
| 				m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); | ||||
| 				if (ref.isDir) { | ||||
| 					m_explorer.dirMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); | ||||
| 				} else { | ||||
| 					m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (nodeOpen) { | ||||
|   | ||||
| @@ -26,6 +26,26 @@ static void generateTypes(ox::TypeStore &ts) noexcept { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static ox::Result<ox::Vector<ox::String>> listAllRecursive(ox::FileSystem &fs, ox::StringViewCR path) { | ||||
| 	// there really isn't much recourse if this function fails, just log it and move on | ||||
| 	auto [out, outErr] = fs.ls(path); | ||||
| 	oxLogError(outErr); | ||||
| 	for (auto const &p : out) { | ||||
| 		auto const [stat, statErr] = fs.stat(path); | ||||
| 		if (statErr) { | ||||
| 			oxLogError(statErr); | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (stat.fileType == ox::FileType::Directory) { | ||||
| 			OX_REQUIRE_M(l, listAllRecursive(fs, sfmt("{}/{}", path, p))); | ||||
| 			for (auto &c : l) { | ||||
| 				out.emplace_back(std::move(c)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return std::move(out); | ||||
| } | ||||
|  | ||||
|  | ||||
| Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir): | ||||
| 	m_kctx(ctx), | ||||
| @@ -78,6 +98,21 @@ ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcep | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept { | ||||
| 	OX_REQUIRE(files, listAllRecursive(m_fs, src)); | ||||
| 	OX_RETURN_ERROR(m_fs.move(src, dest)); | ||||
| 	fileMoved.emit(src, dest, ox::UUID{}); | ||||
| 	for (auto const &op : files) { | ||||
| 		auto const name = | ||||
| 			substr(op, ox::find(op.rbegin(), op.rend(), '/').offset() + 1); | ||||
| 		auto const np = sfmt("{}/{}", dest, name); | ||||
| 		OX_RETURN_ERROR(keel::updatePath(m_kctx, op, np)); | ||||
| 		OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest)); | ||||
| 		fileMoved.emit(src, dest, uuid); | ||||
| 	} | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error Project::deleteItem(ox::StringViewCR path) noexcept { | ||||
| 	OX_REQUIRE(stat, m_fs.stat(path)); | ||||
| 	if (stat.fileType == ox::FileType::Directory) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user