Improve command line and web interface

This commit is contained in:
Lucien Greathouse
2019-09-23 17:54:04 -07:00
parent 5a4189a770
commit 5630cea9a0
8 changed files with 168 additions and 42 deletions

View File

@@ -1,7 +1,13 @@
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use std::{
collections::HashMap,
io::{self, Write},
path::PathBuf,
sync::Arc,
};
use failure::Fail;
use rbx_dom_weak::RbxInstanceProperties;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use crate::{
imfs::{Imfs, RealFetcher, WatchMode},
@@ -46,7 +52,7 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
})
.unwrap_or(DEFAULT_PORT);
println!("Rojo server listening on port {}", port);
let _ = show_start_message(port);
let mut tree = RojoTree::new(InstancePropertiesWithMeta {
properties: RbxInstanceProperties {
@@ -75,20 +81,36 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
server.start(port);
// let receiver = imfs.change_receiver();
Ok(())
}
// while let Ok(change) = receiver.recv() {
// imfs.commit_change(&change)
// .expect("Failed to commit Imfs change");
fn show_start_message(port: u16) -> io::Result<()> {
let mut writer = BufferWriter::stderr(ColorChoice::Auto);
let mut buffer = writer.buffer();
// use notify::DebouncedEvent;
// if let DebouncedEvent::Write(path) = change {
// let contents = imfs.get_contents(path)
// .expect("Failed to read changed path");
writeln!(&mut buffer, "Rojo server listening:")?;
// println!("{:?}", std::str::from_utf8(contents));
// }
// }
write!(&mut buffer, " Address: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "localhost")?;
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, " Port: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "{}", port)?;
writeln!(&mut buffer, "")?;
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, "Visit ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
write!(&mut buffer, "http://localhost:{}/", port)?;
buffer.set_color(&ColorSpec::new())?;
writeln!(&mut buffer, " in your browser for more information.")?;
writer.print(&buffer)?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
// Recursion limit bump is to support Ritz, a JSX-like proc macro used for
// Rojo's web UI currently.
#![recursion_limit = "128"]
#![recursion_limit = "1024"]
#[macro_use]
mod impl_from;

View File

@@ -1,6 +1,7 @@
use std::{
collections::HashSet,
sync::{Arc, Mutex, MutexGuard},
time::Instant,
};
use crate::{
@@ -20,6 +21,10 @@ use crate::{
/// future. `ServeSession` would be roughly the right interface to expose for
/// those cases.
pub struct ServeSession<F> {
/// When the serve session was started. Used only for user-facing
/// diagnostics.
start_time: Instant,
/// The root project for the serve session, if there was one defined.
///
/// This will be defined if a folder with a `default.project.json` file was
@@ -66,6 +71,8 @@ pub struct ServeSession<F> {
/// that handles ServeSession.
impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
pub fn new(imfs: Imfs<F>, tree: RojoTree, root_project: Option<Project>) -> Self {
let start_time = Instant::now();
let session_id = SessionId::new();
let message_queue = MessageQueue::new();
@@ -80,6 +87,7 @@ impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
);
Self {
start_time,
session_id,
root_project,
tree,
@@ -107,6 +115,16 @@ impl<F: ImfsFetcher> ServeSession<F> {
self.session_id
}
pub fn project_name(&self) -> Option<&str> {
self.root_project
.as_ref()
.map(|project| project.name.as_str())
}
pub fn start_time(&self) -> Instant {
self.start_time
}
pub fn serve_place_ids(&self) -> Option<&HashSet<u64>> {
self.root_project
.as_ref()

View File

@@ -1,6 +1,6 @@
//! Defines the HTTP-based UI. These endpoints generally return HTML and SVG.
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
use futures::{future, Future};
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
@@ -15,10 +15,11 @@ use crate::{
},
};
static LOGO: &[u8] = include_bytes!("../../assets/logo-512.png");
static ICON: &[u8] = include_bytes!("../../assets/icon-32.png");
static HOME_CSS: &str = include_str!("../../assets/index.css");
pub struct UiService<F> {
#[allow(unused)] // TODO: Fill out interface service
serve_session: Arc<ServeSession<F>>,
}
@@ -31,6 +32,8 @@ impl<F: ImfsFetcher> Service for UiService<F> {
fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future {
let response = match (request.method(), request.uri().path()) {
(&Method::GET, "/") => self.handle_home(),
(&Method::GET, "/logo.png") => self.handle_logo(),
(&Method::GET, "/icon.png") => self.handle_icon(),
(&Method::GET, "/visualize/rbx") => self.handle_visualize_rbx(),
(&Method::GET, "/visualize/imfs") => self.handle_visualize_imfs(),
(_method, path) => {
@@ -50,28 +53,74 @@ impl<F: ImfsFetcher> UiService<F> {
UiService { serve_session }
}
fn handle_logo(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "image/png")
.body(Body::from(LOGO))
.unwrap()
}
fn handle_icon(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "image/png")
.body(Body::from(ICON))
.unwrap()
}
fn handle_home(&self) -> Response<Body> {
let project_name = self.serve_session.project_name().unwrap_or("<unnamed>");
let uptime = {
let elapsed = self.serve_session.start_time().elapsed();
// Round off all of our sub-second precision to make timestamps
// nicer.
let just_nanos = Duration::from_nanos(elapsed.subsec_nanos() as u64);
let elapsed = elapsed - just_nanos;
humantime::format_duration(elapsed).to_string()
};
let page = html! {
<html>
<head>
<title>"Rojo"</title>
<title>"Rojo Live Server"</title>
<link rel="icon" type="image/png" sizes="32x32" href="/icon.png" />
<style>
{ ritz::UnescapedText::new(HOME_CSS) }
</style>
</head>
<body>
<div class="main">
<h1 class="title">
"Rojo Live Sync is up and running!"
</h1>
<h2 class="subtitle">
"Version " { SERVER_VERSION }
</h2>
<a class="docs" href="https://rojo.space/docs">
"Rojo Documentation"
</a>
</div>
<main class="main">
<header class="header">
<img class="main-logo" src="/logo.png" />
<div class="stats">
<span class="stat">
<span class="stat-name">"Server Version: "</span>
<span class="stat-value">{ SERVER_VERSION }</span>
</span>
<span class="stat">
<span class="stat-name">"Project: "</span>
<span class="stat-value">{ project_name }</span>
</span>
<span class="stat">
<span class="stat-name">"Server Uptime: "</span>
<span class="stat-value">{ uptime.to_string() }</span>
</span>
</div>
</header>
<div class="button-list">
<a class="button" href="https://rojo.space/docs">
"Rojo Documentation"
</a>
<a class="button" href="/visualize/imfs">
"View in-memory filesystem state"
</a>
<a class="button" href="/visualize/rbx">
"View instance tree state"
</a>
</div>
</main>
</body>
</html>
};