From 26fc097672aa8a4142c83b86bc7e4e8154087703 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Tue, 24 Sep 2019 18:04:25 -0700 Subject: [PATCH] Add visualizer for IMFS state --- assets/index.css | 26 ++++++++++++++++++ src/imfs/imfs.rs | 38 ++++++++++++++++++++++++++ src/web/ui.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/assets/index.css b/assets/index.css index b501f6a7..71dd9474 100644 --- a/assets/index.css +++ b/assets/index.css @@ -107,4 +107,30 @@ img { .instance-children { padding: 0.5rem 0 0.5rem 1rem; +} + +.imfs-entry { +} + +.imfs-entry-name { + position: relative; +} + +.imfs-entry-children .imfs-entry-name::before { + content: ""; + width: 0.8rem; + height: 1px; + background-color: #999; + position: absolute; + top: 50%; + left: -1rem; +} + +.imfs-entry-note { + font-style: italic; +} + +.imfs-entry-children { + padding-left: 1rem; + border-left: 1px solid #999; } \ No newline at end of file diff --git a/src/imfs/imfs.rs b/src/imfs/imfs.rs index 875906c6..97eb3806 100644 --- a/src/imfs/imfs.rs +++ b/src/imfs/imfs.rs @@ -303,6 +303,44 @@ impl Imfs { } } +/// Contains extra methods that should only be used for debugging. They're +/// broken out into a separate trait to make it more explicit to depend on them. +pub trait ImfsDebug { + fn debug_is_file(&self, path: &Path) -> bool; + fn debug_contents<'a>(&'a self, path: &Path) -> Option<&'a [u8]>; + fn debug_children<'a>(&'a self, path: &Path) -> Option<(bool, Vec<&'a Path>)>; + fn debug_orphans(&self) -> Vec<&Path>; +} + +impl ImfsDebug for Imfs { + fn debug_is_file(&self, path: &Path) -> bool { + match self.inner.get(path) { + Some(ImfsItem::File(_)) => true, + _ => false, + } + } + + fn debug_contents<'a>(&'a self, path: &Path) -> Option<&'a [u8]> { + match self.inner.get(path) { + Some(ImfsItem::File(file)) => file.contents.as_ref().map(|vec| vec.as_slice()), + _ => None, + } + } + + fn debug_children<'a>(&'a self, path: &Path) -> Option<(bool, Vec<&'a Path>)> { + match self.inner.get(path) { + Some(ImfsItem::Directory(dir)) => { + Some((dir.children_enumerated, self.inner.children(path).unwrap())) + } + _ => None, + } + } + + fn debug_orphans(&self) -> Vec<&Path> { + self.inner.orphans().collect() + } +} + /// A reference to file or folder in an `Imfs`. Can only be produced by the /// entry existing in the Imfs, but can later point to nothing if something /// would invalidate that path. diff --git a/src/web/ui.rs b/src/web/ui.rs index b29f44ad..a2b95713 100644 --- a/src/web/ui.rs +++ b/src/web/ui.rs @@ -1,6 +1,6 @@ //! Defines the HTTP-based UI. These endpoints generally return HTML and SVG. -use std::{sync::Arc, time::Duration}; +use std::{path::Path, sync::Arc, time::Duration}; use futures::{future, Future}; use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode}; @@ -8,7 +8,7 @@ use rbx_dom_weak::{RbxId, RbxValue}; use ritz::{html, Fragment, HtmlContent}; use crate::{ - imfs::ImfsFetcher, + imfs::{Imfs, ImfsDebug, ImfsFetcher}, serve_session::ServeSession, snapshot::RojoTree, web::{ @@ -96,8 +96,18 @@ impl UiService { } fn handle_show_imfs(&self) -> Response { + let imfs = self.serve_session.imfs(); + + let orphans: Vec<_> = imfs + .debug_orphans() + .into_iter() + .map(|path| Self::render_imfs_path(&imfs, path, true)) + .collect(); + let page = self.normal_page(html! { - "TODO /show/imfs" +
+ { Fragment::new(orphans) } +
}); Response::builder() @@ -106,6 +116,61 @@ impl UiService { .unwrap() } + fn render_imfs_path(imfs: &Imfs, path: &Path, is_root: bool) -> HtmlContent<'static> { + let is_file = imfs.debug_is_file(path); + + let (note, children) = if is_file { + (HtmlContent::None, Vec::new()) + } else { + let (is_exhaustive, mut children) = imfs.debug_children(path).unwrap(); + + // Sort files above directories, then sort how Path does after that. + children.sort_unstable_by(|a, b| { + let a_is_file = imfs.debug_is_file(a); + let b_is_file = imfs.debug_is_file(b); + + b_is_file.cmp(&a_is_file).then_with(|| a.cmp(b)) + }); + + let children: Vec<_> = children + .into_iter() + .map(|child| Self::render_imfs_path(imfs, child, false)) + .collect(); + + let note = if is_exhaustive { + HtmlContent::None + } else { + html!({ " (non-exhaustive)" }) + }; + + (note, children) + }; + + // For root entries, we want the full path to contextualize the path. + let mut name = if is_root { + path.to_str().unwrap().to_owned() + } else { + path.file_name().unwrap().to_str().unwrap().to_owned() + }; + + // Directories should end with `/` in the UI to mark them. + if !is_file && !name.ends_with('/') && !name.ends_with('\\') { + name.push('/'); + } + + html! { +
+
+ { name } + { note } +
+
+ { Fragment::new(children) } +
+
+ } + } + fn instance(tree: &RojoTree, id: RbxId) -> HtmlContent<'_> { let instance = tree.get_instance(id).unwrap(); let children_list: Vec<_> = instance