[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