Add visualizer for IMFS state

This commit is contained in:
Lucien Greathouse
2019-09-24 18:04:25 -07:00
parent 0f2e2406e8
commit 26fc097672
3 changed files with 132 additions and 3 deletions

View File

@@ -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;
}

View File

@@ -303,6 +303,44 @@ impl<F: ImfsFetcher> Imfs<F> {
}
}
/// 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<F> ImfsDebug for Imfs<F> {
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.

View File

@@ -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<F: ImfsFetcher> UiService<F> {
}
fn handle_show_imfs(&self) -> Response<Body> {
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"
<div>
{ Fragment::new(orphans) }
</div>
});
Response::builder()
@@ -106,6 +116,61 @@ impl<F: ImfsFetcher> UiService<F> {
.unwrap()
}
fn render_imfs_path(imfs: &Imfs<F>, 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! {
<div class="imfs-entry">
<div>
<span class="imfs-entry-name">{ name }</span>
<span class="imfs-entry-note">{ note }</span>
</div>
<div class="imfs-entry-children">
{ Fragment::new(children) }
</div>
</div>
}
}
fn instance(tree: &RojoTree, id: RbxId) -> HtmlContent<'_> {
let instance = tree.get_instance(id).unwrap();
let children_list: Vec<_> = instance