wip: move to tokio/axum/etc
This commit is contained in:
1 parent
5fdb14446b
commit
07efeda124
21 files changed
+483
-191
No files matched your search
Vendored
+1
-3
@@ -1,3 +1 @@
|
|||||||
{
|
{}
|
||||||
"rust-analyzer.inlayHints.enable": true
|
|
||||||
}
|
|
||||||
+11
-14
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-studio"
|
name = "dioxus-studio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
||||||
"license" = "MIT/Apache-2.0"
|
"license" = "MIT/Apache-2.0"
|
||||||
@@ -14,25 +14,22 @@ log = "0.4.13"
|
|||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
wasm-bindgen-cli-support = "0.2.78"
|
wasm-bindgen-cli-support = "0.2.78"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
argh = "0.1.4"
|
|
||||||
serde = "1.0.120"
|
|
||||||
serde_json = "1.0.61"
|
|
||||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
|
||||||
tide = "0.16.0"
|
|
||||||
fs_extra = "1.2.0"
|
|
||||||
|
|
||||||
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
fs_extra = "1.2.0"
|
||||||
cargo_toml = "0.10.0"
|
cargo_toml = "0.10.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
notify = "5.0.0-pre.4"
|
notify = "5.0.0-pre.4"
|
||||||
rjdebounce = "0.2.1"
|
|
||||||
tempfile = "3.2.0"
|
|
||||||
html_parser = "0.6.2"
|
html_parser = "0.6.2"
|
||||||
|
tui = { version = "0.16.0", features = ["crossterm"] }
|
||||||
tui = { version = "0.15.0", features = ["crossterm"] }
|
crossterm = "0.22.1"
|
||||||
crossterm = "0.19.0"
|
binary-install = "0.0.2"
|
||||||
tui-template = { git = "https://github.com/jkelleyrtp/tui-builder.git" }
|
convert_case = "0.5.0"
|
||||||
|
structopt = "0.3.25"
|
||||||
|
cargo_metadata = "0.14.1"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
name = "dioxus"
|
name = "dioxus"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
asd
|
||||||
|
- [ ] use a TUI for the dev server aspect of things
|
||||||
|
- [ ] allow any index.html
|
||||||
|
- [ ] use wasmopt for production builds
|
||||||
|
- [ ] pass arguments through to `cargo`
|
||||||
|
- [ ] figure out a bundling strategy
|
||||||
|
- [ ] an external configuration
|
||||||
|
|
||||||
|
|
||||||
|
should we just use trunk?
|
||||||
+38
-21
@@ -3,26 +3,13 @@ use crate::{
|
|||||||
config::{CrateConfig, ExecutableType},
|
config::{CrateConfig, ExecutableType},
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
};
|
};
|
||||||
use log::{info, warn};
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use wasm_bindgen_cli_support::Bindgen;
|
use wasm_bindgen_cli_support::Bindgen;
|
||||||
|
|
||||||
pub struct BuildConfig {}
|
pub fn build(config: &CrateConfig) -> Result<()> {
|
||||||
impl Into<BuildConfig> for BuildOptions {
|
|
||||||
fn into(self) -> BuildConfig {
|
|
||||||
BuildConfig {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Default for BuildConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|
||||||
/*
|
/*
|
||||||
[1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
|
[1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
|
||||||
[2] Generate the appropriate build folders
|
[2] Generate the appropriate build folders
|
||||||
@@ -43,7 +30,7 @@ pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|||||||
let t_start = std::time::Instant::now();
|
let t_start = std::time::Instant::now();
|
||||||
|
|
||||||
// [1] Build the .wasm module
|
// [1] Build the .wasm module
|
||||||
info!("Running build commands...");
|
log::info!("Running build commands...");
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
cmd.current_dir(&crate_dir)
|
cmd.current_dir(&crate_dir)
|
||||||
.arg("build")
|
.arg("build")
|
||||||
@@ -66,7 +53,7 @@ pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|||||||
let output = child.wait()?;
|
let output = child.wait()?;
|
||||||
|
|
||||||
if output.success() {
|
if output.success() {
|
||||||
info!("Build complete!");
|
log::info!("Build complete!");
|
||||||
} else {
|
} else {
|
||||||
log::error!("Build failed!");
|
log::error!("Build failed!");
|
||||||
let mut reason = String::new();
|
let mut reason = String::new();
|
||||||
@@ -87,12 +74,10 @@ pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|||||||
|
|
||||||
let input_path = match executable {
|
let input_path = match executable {
|
||||||
ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
|
ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
|
||||||
// .join("wasm32-unknown-unknown/release")
|
|
||||||
.join(format!("wasm32-unknown-unknown/{}", release_type))
|
.join(format!("wasm32-unknown-unknown/{}", release_type))
|
||||||
.join(format!("{}.wasm", name)),
|
.join(format!("{}.wasm", name)),
|
||||||
|
|
||||||
ExecutableType::Example(name) => target_dir
|
ExecutableType::Example(name) => target_dir
|
||||||
// .join("wasm32-unknown-unknown/release/examples")
|
|
||||||
.join(format!("wasm32-unknown-unknown/{}/examples", release_type))
|
.join(format!("wasm32-unknown-unknown/{}/examples", release_type))
|
||||||
.join(format!("{}.wasm", name)),
|
.join(format!("{}.wasm", name)),
|
||||||
};
|
};
|
||||||
@@ -113,7 +98,7 @@ pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|||||||
|
|
||||||
// [5] Generate the html file with the module name
|
// [5] Generate the html file with the module name
|
||||||
// TODO: support names via options
|
// TODO: support names via options
|
||||||
info!("Writing to '{:#?}' directory...", out_dir);
|
log::info!("Writing to '{:#?}' directory...", out_dir);
|
||||||
let mut file = std::fs::File::create(out_dir.join("index.html"))?;
|
let mut file = std::fs::File::create(out_dir.join("index.html"))?;
|
||||||
file.write_all(gen_page("./wasm/module.js").as_str().as_bytes())?;
|
file.write_all(gen_page("./wasm/module.js").as_str().as_bytes())?;
|
||||||
|
|
||||||
@@ -121,7 +106,7 @@ pub fn build(config: &CrateConfig, _build_config: &BuildConfig) -> Result<()> {
|
|||||||
match fs_extra::dir::copy(static_dir, out_dir, ©_options) {
|
match fs_extra::dir::copy(static_dir, out_dir, ©_options) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
warn!("Error copying dir");
|
log::warn!("Error copying dir");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +124,7 @@ fn gen_page(module: &str) -> String {
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="dioxusroot">
|
<div id="main">
|
||||||
</div>
|
</div>
|
||||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
@@ -153,3 +138,35 @@ fn gen_page(module: &str) -> String {
|
|||||||
module
|
module
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use binary_install::{Cache, Download};
|
||||||
|
|
||||||
|
// /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a
|
||||||
|
// /// precompiled binary.
|
||||||
|
// ///
|
||||||
|
// /// Returns `Some` if a binary was found or it was successfully downloaded.
|
||||||
|
// /// Returns `None` if a binary wasn't found in `PATH` and this platform doesn't
|
||||||
|
// /// have precompiled binaries. Returns an error if we failed to download the
|
||||||
|
// /// binary.
|
||||||
|
// pub fn find_wasm_opt(
|
||||||
|
// cache: &Cache,
|
||||||
|
// install_permitted: bool,
|
||||||
|
// ) -> Result<install::Status, failure::Error> {
|
||||||
|
// // First attempt to look up in PATH. If found assume it works.
|
||||||
|
// if let Ok(path) = which::which("wasm-opt") {
|
||||||
|
// PBAR.info(&format!("found wasm-opt at {:?}", path));
|
||||||
|
|
||||||
|
// match path.as_path().parent() {
|
||||||
|
// Some(path) => return Ok(install::Status::Found(Download::at(path))),
|
||||||
|
// None => {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let version = "version_78";
|
||||||
|
// Ok(install::download_prebuilt(
|
||||||
|
// &install::Tool::WasmOpt,
|
||||||
|
// cache,
|
||||||
|
// version,
|
||||||
|
// install_permitted,
|
||||||
|
// )?)
|
||||||
|
// }
|
||||||
@@ -90,6 +90,10 @@ pub struct TranslateOptions {
|
|||||||
/// an example in the crate
|
/// an example in the crate
|
||||||
#[argh(option, short = 't')]
|
#[argh(option, short = 't')]
|
||||||
pub text: Option<String>,
|
pub text: Option<String>,
|
||||||
|
|
||||||
|
/// whether or not to jump
|
||||||
|
#[argh(switch, short = 'c')]
|
||||||
|
pub component: bool,
|
||||||
}
|
}
|
||||||
/// 🛠 Translate some 3rd party template into rsx
|
/// 🛠 Translate some 3rd party template into rsx
|
||||||
#[derive(FromArgs, PartialEq, Debug, Clone)]
|
#[derive(FromArgs, PartialEq, Debug, Clone)]
|
||||||
|
|||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
File renamed without changes.
@@ -1,37 +1,33 @@
|
|||||||
use crate::{builder::BuildConfig, cli::DevelopOptions, config::CrateConfig, error::Result};
|
use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
|
||||||
use async_std::prelude::FutureExt;
|
use async_std::prelude::FutureExt;
|
||||||
|
|
||||||
use async_std::future;
|
|
||||||
use async_std::prelude::*;
|
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use tide::http::mime::HTML;
|
||||||
|
use tide::http::Mime;
|
||||||
|
|
||||||
pub struct DevelopConfig {}
|
pub struct DevelopState {
|
||||||
impl Into<DevelopConfig> for DevelopOptions {
|
//
|
||||||
fn into(self) -> DevelopConfig {
|
reload_on_change: bool,
|
||||||
DevelopConfig {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrStatus = Arc<AtomicBool>;
|
pub async fn develop(options: DevelopOptions) -> Result<()> {
|
||||||
|
//
|
||||||
pub async fn start(config: &CrateConfig, _options: &DevelopConfig) -> Result<()> {
|
|
||||||
log::info!("Starting development server 🚀");
|
log::info!("Starting development server 🚀");
|
||||||
|
let mut cfg = CrateConfig::new()?;
|
||||||
|
cfg.with_develop_options(&options);
|
||||||
|
|
||||||
let CrateConfig { out_dir, .. } = config;
|
let out_dir = cfg.out_dir.clone();
|
||||||
|
|
||||||
let is_err = Arc::new(AtomicBool::new(false));
|
let is_err = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
// Spawn the server onto a seperate task
|
// Spawn the server onto a seperate task
|
||||||
// This lets the task progress while we handle file updates
|
// This lets the task progress while we handle file updates
|
||||||
let server = async_std::task::spawn(launch_server(out_dir.clone(), is_err.clone()));
|
let server = async_std::task::spawn(launch_server(out_dir, is_err.clone()));
|
||||||
|
let watcher = async_std::task::spawn(watch_directory(cfg.clone(), is_err.clone()));
|
||||||
let watcher = async_std::task::spawn(watch_directory(config.clone(), is_err.clone()));
|
|
||||||
|
|
||||||
match server.race(watcher).await {
|
match server.race(watcher).await {
|
||||||
Err(e) => log::warn!("Error running development server, {:?}", e),
|
Err(e) => log::warn!("Error running development server, {:?}", e),
|
||||||
@@ -70,10 +66,8 @@ async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
|
|||||||
Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
let build_config = BuildConfig::default();
|
|
||||||
|
|
||||||
'run: loop {
|
'run: loop {
|
||||||
match crate::builder::build(&config, &build_config) {
|
match crate::builder::build(&config) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
@@ -100,6 +94,9 @@ async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
|||||||
let _workspace_dir = crate::cargo::workspace_root()?;
|
let _workspace_dir = crate::cargo::workspace_root()?;
|
||||||
|
|
||||||
let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
||||||
|
|
||||||
|
let file_path = format!("{}/index.html", outdir.display());
|
||||||
|
log::info!("Serving {}", file_path);
|
||||||
let p = outdir.display().to_string();
|
let p = outdir.display().to_string();
|
||||||
|
|
||||||
app.at("/")
|
app.at("/")
|
||||||
@@ -108,22 +105,27 @@ async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
|||||||
let state = req.state();
|
let state = req.state();
|
||||||
|
|
||||||
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
true => Ok(tide::Body::from_string(format!(
|
true => {
|
||||||
include_str!("./err.html"),
|
//
|
||||||
err = "_"
|
let mut resp =
|
||||||
))),
|
tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
|
||||||
|
resp.set_mime(HTML);
|
||||||
|
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
false => {
|
false => {
|
||||||
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.serve_dir(p)?;
|
.serve_dir(p)?;
|
||||||
|
// .serve_file(file_path)
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
let port = "8080";
|
let port = "8080";
|
||||||
// let serve_addr = format!("0.0.0.0:{}", port);
|
|
||||||
let serve_addr = format!("127.0.0.1:{}", port);
|
let serve_addr = format!("127.0.0.1:{}", port);
|
||||||
|
|
||||||
info!("App available at http://{}", serve_addr);
|
info!("App available at http://{}/", serve_addr);
|
||||||
app.listen(serve_addr).await?;
|
app.listen(serve_addr).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -136,6 +138,9 @@ struct ServerState {
|
|||||||
serv_path: PathBuf,
|
serv_path: PathBuf,
|
||||||
is_err: ErrStatus,
|
is_err: ErrStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrStatus = Arc<AtomicBool>;
|
||||||
|
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
||||||
Self { serv_path, is_err }
|
Self { serv_path, is_err }
|
||||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1,86 +1,7 @@
|
|||||||
//! It's better to store all the configuration in one spot
|
//! It's better to store all the configuration in one spot
|
||||||
//!
|
|
||||||
use tui_template::tuiapp::TuiApp;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
use std::{any::Any, io::Write, path::PathBuf, process::Command};
|
|
||||||
|
|
||||||
pub struct Studio {
|
|
||||||
command: LaunchOptions,
|
|
||||||
headless: bool,
|
|
||||||
example: Option<String>,
|
|
||||||
outdir: Option<String>,
|
|
||||||
release: bool,
|
|
||||||
hydrate: Option<String>,
|
|
||||||
template: Option<String>,
|
|
||||||
translate_file: Option<String>,
|
|
||||||
crate_config: Option<CrateConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Studio {
|
|
||||||
pub fn new(command: LaunchOptions) -> Self {
|
|
||||||
let headless = true;
|
|
||||||
let release = false;
|
|
||||||
let example = None;
|
|
||||||
let outdir = None;
|
|
||||||
let hydrate = None;
|
|
||||||
let template = None;
|
|
||||||
let translate_file = None;
|
|
||||||
let crate_config = None;
|
|
||||||
|
|
||||||
match command.command {
|
|
||||||
LaunchCommand::Translate(_) => todo!(),
|
|
||||||
LaunchCommand::Develop(_) => todo!(),
|
|
||||||
LaunchCommand::Build(_) => todo!(),
|
|
||||||
LaunchCommand::Test(_) => todo!(),
|
|
||||||
LaunchCommand::Publish(_) => todo!(),
|
|
||||||
LaunchCommand::Studio(StudioOptions { .. }) => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
command,
|
|
||||||
headless,
|
|
||||||
example,
|
|
||||||
outdir,
|
|
||||||
release,
|
|
||||||
hydrate,
|
|
||||||
template,
|
|
||||||
translate_file,
|
|
||||||
crate_config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start(self) -> Result<()> {
|
|
||||||
match self.command.command {
|
|
||||||
LaunchCommand::Develop(_) => todo!(),
|
|
||||||
LaunchCommand::Build(_) => todo!(),
|
|
||||||
LaunchCommand::Translate(_) => todo!(),
|
|
||||||
LaunchCommand::Test(_) => todo!(),
|
|
||||||
LaunchCommand::Publish(_) => todo!(),
|
|
||||||
LaunchCommand::Studio(_) => self.launch_studio().await?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn launch_studio(mut self) -> Result<()> {
|
|
||||||
let task = async_std::task::spawn_blocking(|| async move {
|
|
||||||
let mut app = TuiStudio {
|
|
||||||
cfg: self,
|
|
||||||
hooks: vec![],
|
|
||||||
hook_idx: 0,
|
|
||||||
};
|
|
||||||
app.launch(250).expect("tui app crashed :(");
|
|
||||||
});
|
|
||||||
let r = task.await.await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::{Backend, CrosstermBackend},
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
symbols,
|
symbols,
|
||||||
@@ -90,12 +11,61 @@ use tui::{
|
|||||||
Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List,
|
Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List,
|
||||||
ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
|
ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
|
||||||
},
|
},
|
||||||
Frame,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TuiStudio {
|
use crate::*;
|
||||||
cfg: Studio,
|
use std::{any::Any, io::Write, path::PathBuf, process::Command};
|
||||||
|
|
||||||
|
pub struct Cfg {
|
||||||
|
command: LaunchOptions,
|
||||||
|
headless: bool,
|
||||||
|
example: Option<String>,
|
||||||
|
outdir: Option<String>,
|
||||||
|
release: bool,
|
||||||
|
hydrate: Option<String>,
|
||||||
|
template: Option<String>,
|
||||||
|
translate_file: Option<String>,
|
||||||
|
crate_config: Option<CrateConfig>,
|
||||||
|
should_quit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(options: DevelopOptions) -> Result<()> {
|
||||||
|
let mut state = Cfg {
|
||||||
|
command: todo!(),
|
||||||
|
headless: todo!(),
|
||||||
|
example: todo!(),
|
||||||
|
outdir: todo!(),
|
||||||
|
release: todo!(),
|
||||||
|
hydrate: todo!(),
|
||||||
|
template: todo!(),
|
||||||
|
translate_file: todo!(),
|
||||||
|
crate_config: todo!(),
|
||||||
|
should_quit: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
|
let backend = CrosstermBackend::new(std::io::stdout());
|
||||||
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
|
// Setup input handling
|
||||||
|
// let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||||
|
let tick_rate = std::time::Duration::from_millis(100);
|
||||||
|
|
||||||
|
let mut prev_time = std::time::Instant::now();
|
||||||
|
while !state.should_quit {
|
||||||
|
let next_time = prev_time + tick_rate;
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
let diff = next_time - std::time::Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TuiStudio {
|
||||||
|
cfg: Cfg,
|
||||||
hook_idx: usize,
|
hook_idx: usize,
|
||||||
hooks: Vec<Box<dyn Any>>,
|
hooks: Vec<Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
@@ -112,7 +82,7 @@ impl TuiStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TuiApp for TuiStudio {
|
impl TuiStudio {
|
||||||
fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> {
|
fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> {
|
||||||
match action {
|
match action {
|
||||||
crossterm::event::Event::Key(_) => {}
|
crossterm::event::Event::Key(_) => {}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,181 @@
|
|||||||
|
//! Intelligently converts html to rsx with appropraite transformations and extractions.
|
||||||
|
//!
|
||||||
|
//! - [*] Creates a component
|
||||||
|
//! - [ ] Extracts svgs
|
||||||
|
//! - [ ] Attempts to extract lists
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
io::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use html_parser::{Dom, Element, Node};
|
||||||
|
|
||||||
|
pub fn convert_html_to_component(html: &str) -> Result<ComponentRenderer> {
|
||||||
|
Ok(ComponentRenderer {
|
||||||
|
dom: Dom::parse(html)?,
|
||||||
|
icon_index: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComponentRenderer {
|
||||||
|
dom: Dom,
|
||||||
|
icon_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ComponentRenderer {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
r##"
|
||||||
|
fn component(cx: Scope) -> Element {{
|
||||||
|
cx.render(rsx!("##
|
||||||
|
)?;
|
||||||
|
let mut svg_nodes = vec![];
|
||||||
|
|
||||||
|
let mut svg_idx = 0;
|
||||||
|
for child in &self.dom.children {
|
||||||
|
render_child(f, child, 2, &mut svg_nodes, true, &mut svg_idx)?;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
r##" ))
|
||||||
|
}}"##
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if svg_idx == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "\n\nmod icons {{")?;
|
||||||
|
|
||||||
|
let mut id = 0;
|
||||||
|
while let Some(svg) = svg_nodes.pop() {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
r##" pub(super) fn icon_{}(cx: Scope) -> Element {{
|
||||||
|
cx.render(rsx!("##,
|
||||||
|
id
|
||||||
|
)?;
|
||||||
|
write_tabs(f, 3)?;
|
||||||
|
|
||||||
|
render_element(f, svg, 3, &mut svg_nodes, false, &mut 0)?;
|
||||||
|
writeln!(f, "\t\t))\n\t}}\n")?;
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "}}")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_child<'a>(
|
||||||
|
f: &mut Formatter<'_>,
|
||||||
|
child: &'a Node,
|
||||||
|
il: u32,
|
||||||
|
svg_buffer: &mut Vec<&'a Element>,
|
||||||
|
skip_svg: bool,
|
||||||
|
svg_idx: &mut usize,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
write_tabs(f, il)?;
|
||||||
|
match child {
|
||||||
|
Node::Text(t) => writeln!(f, "\"{}\"", t)?,
|
||||||
|
Node::Comment(e) => writeln!(f, "/* {} */", e)?,
|
||||||
|
Node::Element(el) => render_element(f, el, il, svg_buffer, skip_svg, svg_idx)?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_element<'a>(
|
||||||
|
f: &mut Formatter<'_>,
|
||||||
|
el: &'a Element,
|
||||||
|
il: u32,
|
||||||
|
svg_buffer: &mut Vec<&'a Element>,
|
||||||
|
skip_svg: bool,
|
||||||
|
svg_idx: &mut usize,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
if el.name == "svg" && skip_svg {
|
||||||
|
svg_buffer.push(el);
|
||||||
|
// todo: attach the right icon ID
|
||||||
|
writeln!(f, "icons::icon_{} {{}}", svg_idx)?;
|
||||||
|
*svg_idx += 1;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the tag
|
||||||
|
write!(f, "{} {{ ", &el.name)?;
|
||||||
|
|
||||||
|
// todo: dioxus will eventually support classnames
|
||||||
|
// for now, just write them with a space between each
|
||||||
|
let class_iter = &mut el.classes.iter();
|
||||||
|
if let Some(first_class) = class_iter.next() {
|
||||||
|
write!(f, "class: \"{}", first_class)?;
|
||||||
|
for next_class in class_iter {
|
||||||
|
write!(f, " {}", next_class)?;
|
||||||
|
}
|
||||||
|
write!(f, "\",")?;
|
||||||
|
}
|
||||||
|
write!(f, "\n")?;
|
||||||
|
|
||||||
|
// write the attributes
|
||||||
|
if let Some(id) = &el.id {
|
||||||
|
write_tabs(f, il + 1)?;
|
||||||
|
writeln!(f, "id: \"{}\",", id)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, value) in &el.attributes {
|
||||||
|
write_tabs(f, il + 1)?;
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
if name.chars().any(|ch| ch.is_ascii_uppercase() || ch == '-') {
|
||||||
|
let new_name = name.to_case(Case::Snake);
|
||||||
|
match value {
|
||||||
|
Some(val) => writeln!(f, "{}: \"{}\",", new_name, val)?,
|
||||||
|
None => writeln!(f, "{}: \"\",", new_name)?,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match name.as_str() {
|
||||||
|
"for" | "async" | "type" | "as" => write!(f, "r#")?,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(val) => writeln!(f, "{}: \"{}\",", name, val)?,
|
||||||
|
None => writeln!(f, "{}: \"\",", name)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now the children
|
||||||
|
for child in &el.children {
|
||||||
|
render_child(f, child, il + 1, svg_buffer, skip_svg, svg_idx)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the tag
|
||||||
|
write_tabs(f, il)?;
|
||||||
|
writeln!(f, "}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_tabs(f: &mut Formatter, num: u32) -> std::fmt::Result {
|
||||||
|
for _ in 0..num {
|
||||||
|
write!(f, " ")?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_svgs() {
|
||||||
|
let st = include_str!("../../tests/svg.html");
|
||||||
|
|
||||||
|
let out = format!("{:}", convert_html_to_component(st).unwrap());
|
||||||
|
dbg!(&out);
|
||||||
|
|
||||||
|
std::fs::File::create("svg_rsx.rs")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(out.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
@@ -33,13 +33,25 @@ impl Display for RsxRenderer {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if self.as_call {
|
if self.as_call {
|
||||||
writeln!(f, r##"use dioxus::prelude::*;"##)?;
|
writeln!(f, r##"use dioxus::prelude::*;"##)?;
|
||||||
writeln!(f, r##"const Component: FC<()> = |cx| cx.render(rsx!{{"##)?;
|
writeln!(
|
||||||
|
f,
|
||||||
|
r##"
|
||||||
|
fn component(cx: Scope) -> Element {{
|
||||||
|
cx.render(rsx!(
|
||||||
|
"##
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
for child in &self.dom.children {
|
for child in &self.dom.children {
|
||||||
render_child(f, child, 1)?;
|
render_child(f, child, 1)?;
|
||||||
}
|
}
|
||||||
if self.as_call {
|
if self.as_call {
|
||||||
write!(f, r##"}});"##)?;
|
write!(
|
||||||
|
f,
|
||||||
|
r##"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"##
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -74,9 +86,24 @@ fn render_child(f: &mut Formatter<'_>, child: &Node, il: u32) -> std::fmt::Resul
|
|||||||
|
|
||||||
for (name, value) in &el.attributes {
|
for (name, value) in &el.attributes {
|
||||||
write_tabs(f, il + 1)?;
|
write_tabs(f, il + 1)?;
|
||||||
match value {
|
|
||||||
Some(val) => writeln!(f, "{}: \"{}\",", name, val)?,
|
use convert_case::{Case, Casing};
|
||||||
None => writeln!(f, "{}: \"\",", name)?,
|
if name.chars().any(|ch| ch.is_ascii_uppercase() || ch == '-') {
|
||||||
|
let new_name = name.to_case(Case::Snake);
|
||||||
|
match value {
|
||||||
|
Some(val) => writeln!(f, "{}: \"{}\",", new_name, val)?,
|
||||||
|
None => writeln!(f, "{}: \"\",", new_name)?,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match name.as_str() {
|
||||||
|
"for" | "async" | "type" | "as" => write!(f, "r#")?,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(val) => writeln!(f, "{}: \"{}\",", name, val)?,
|
||||||
|
None => writeln!(f, "{}: \"\",", name)?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
mod builder;
|
|
||||||
mod cargo;
|
|
||||||
mod cli;
|
|
||||||
mod config;
|
|
||||||
mod develop;
|
|
||||||
mod error;
|
|
||||||
mod logging;
|
|
||||||
mod studio;
|
|
||||||
mod translate;
|
|
||||||
mod watch;
|
|
||||||
|
|
||||||
pub use builder::*;
|
|
||||||
pub use cargo::*;
|
|
||||||
pub use cli::*;
|
|
||||||
pub use config::*;
|
|
||||||
pub use develop::*;
|
|
||||||
pub use error::*;
|
|
||||||
pub use logging::*;
|
|
||||||
pub use studio::*;
|
|
||||||
pub use translate::*;
|
|
||||||
pub use watch::*;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
pub fn set_up_logging() {
|
pub fn set_up_logging() {
|
||||||
// configure colors for the whole line
|
// configure colors for the whole line
|
||||||
@@ -29,18 +28,7 @@ pub fn set_up_logging() {
|
|||||||
message = message,
|
message = message,
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
// set the default log level. to filter out verbose log messages from dependencies, set
|
|
||||||
// this to Warn and overwrite the log level for your crate.
|
|
||||||
.level(log::LevelFilter::Info)
|
.level(log::LevelFilter::Info)
|
||||||
// .level(log::LevelFilter::Warn)
|
|
||||||
// change log levels for individual modules. Note: This looks for the record's target
|
|
||||||
// field which defaults to the module path but can be overwritten with the `target`
|
|
||||||
// parameter:
|
|
||||||
// `info!(target="special_target", "This log message is about special_target");`
|
|
||||||
// .level_for("dioxus", log::LevelFilter::Debug)
|
|
||||||
// .level_for("dioxus", log::LevelFilter::Info)
|
|
||||||
// .level_for("pretty_colored", log::LevelFilter::Trace)
|
|
||||||
// output to stdout
|
|
||||||
.chain(std::io::stdout())
|
.chain(std::io::stdout())
|
||||||
.apply()
|
.apply()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
+89
-4
@@ -1,12 +1,97 @@
|
|||||||
use dioxus_studio::{set_up_logging, LaunchCommand, LaunchOptions};
|
mod builder;
|
||||||
|
mod cargo;
|
||||||
|
mod cli;
|
||||||
|
mod config;
|
||||||
|
mod error;
|
||||||
|
mod logging;
|
||||||
|
mod helpers {
|
||||||
|
pub mod extract_svgs;
|
||||||
|
pub mod to_component;
|
||||||
|
pub mod translate;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod watch;
|
||||||
|
mod develop {
|
||||||
|
pub mod develop;
|
||||||
|
pub mod draw;
|
||||||
|
pub mod events;
|
||||||
|
pub mod studio;
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() -> dioxus_studio::Result<()> {
|
async fn main() -> Result<()> {
|
||||||
set_up_logging();
|
set_up_logging();
|
||||||
|
let args = Args::from_args();
|
||||||
|
|
||||||
let opts: LaunchOptions = argh::from_env();
|
match args.command {
|
||||||
|
LaunchCommand::Develop(cfg) => {
|
||||||
|
develop::develop::develop(cfg).await?;
|
||||||
|
}
|
||||||
|
// LaunchCommand::Develop(cfg) => develop::studio::start(cfg).await?,
|
||||||
|
LaunchCommand::Build(opts) => {
|
||||||
|
let mut cfg = CrateConfig::new()?;
|
||||||
|
cfg.with_build_options(&opts);
|
||||||
|
builder::build(&cfg)?;
|
||||||
|
}
|
||||||
|
|
||||||
dioxus_studio::Studio::new(opts).start().await?;
|
LaunchCommand::Translate(cfg) => {
|
||||||
|
let TranslateOptions {
|
||||||
|
file,
|
||||||
|
text,
|
||||||
|
component,
|
||||||
|
} = cfg;
|
||||||
|
|
||||||
|
match component {
|
||||||
|
true => {
|
||||||
|
let f = helpers::to_component::convert_html_to_component(&text.unwrap())?;
|
||||||
|
println!("{}", f);
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let renderer = match (file, text) {
|
||||||
|
(None, Some(text)) => translate::translate_from_html_to_rsx(&text, false)?,
|
||||||
|
(Some(file), None) => translate::translate_from_html_file(&file)?,
|
||||||
|
_ => panic!("Must select either file or text - not both or none!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{}", renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
todo!("Those commands are not yet supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build, bundle & ship your Rust WASM application to the web.
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(name = "trunk")]
|
||||||
|
struct Args {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
command: TrunkSubcommands,
|
||||||
|
/// Path to the Trunk config file [default: Trunk.toml]
|
||||||
|
#[structopt(long, parse(from_os_str), env = "TRUNK_CONFIG")]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
/// Enable verbose logging.
|
||||||
|
#[structopt(short)]
|
||||||
|
pub v: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
enum TrunkSubcommands {
|
||||||
|
/// Build the Rust WASM app and all of its assets.
|
||||||
|
Build(cmd::build::Build),
|
||||||
|
/// Build & watch the Rust WASM app and all of its assets.
|
||||||
|
Watch(cmd::watch::Watch),
|
||||||
|
/// Build, watch & serve the Rust WASM app and all of its assets.
|
||||||
|
Serve(cmd::serve::Serve),
|
||||||
|
/// Clean output artifacts.
|
||||||
|
Clean(cmd::clean::Clean),
|
||||||
|
/// Trunk config controls.
|
||||||
|
Config(cmd::config::Config),
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<div>
|
||||||
|
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
Reference in new issue
Block a user