mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-23 06:05:24 +00:00
Add visualizer for IMFS state
This commit is contained in:
@@ -107,4 +107,30 @@ img {
|
|||||||
|
|
||||||
.instance-children {
|
.instance-children {
|
||||||
padding: 0.5rem 0 0.5rem 1rem;
|
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
|
/// 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
|
/// entry existing in the Imfs, but can later point to nothing if something
|
||||||
/// would invalidate that path.
|
/// would invalidate that path.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Defines the HTTP-based UI. These endpoints generally return HTML and SVG.
|
//! 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 futures::{future, Future};
|
||||||
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
|
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 ritz::{html, Fragment, HtmlContent};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
imfs::ImfsFetcher,
|
imfs::{Imfs, ImfsDebug, ImfsFetcher},
|
||||||
serve_session::ServeSession,
|
serve_session::ServeSession,
|
||||||
snapshot::RojoTree,
|
snapshot::RojoTree,
|
||||||
web::{
|
web::{
|
||||||
@@ -96,8 +96,18 @@ impl<F: ImfsFetcher> UiService<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_show_imfs(&self) -> Response<Body> {
|
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! {
|
let page = self.normal_page(html! {
|
||||||
"TODO /show/imfs"
|
<div>
|
||||||
|
{ Fragment::new(orphans) }
|
||||||
|
</div>
|
||||||
});
|
});
|
||||||
|
|
||||||
Response::builder()
|
Response::builder()
|
||||||
@@ -106,6 +116,61 @@ impl<F: ImfsFetcher> UiService<F> {
|
|||||||
.unwrap()
|
.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<'_> {
|
fn instance(tree: &RojoTree, id: RbxId) -> HtmlContent<'_> {
|
||||||
let instance = tree.get_instance(id).unwrap();
|
let instance = tree.get_instance(id).unwrap();
|
||||||
let children_list: Vec<_> = instance
|
let children_list: Vec<_> = instance
|
||||||
|
|||||||
Reference in New Issue
Block a user