forked from rojo-rbx/rojo
Add visualizer for IMFS state
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user