wip: studio upgrades

This commit is contained in:
Jonathan Kelley committed 2021-12-29 12:03:18 -05:00
1 parent 0966653630
commit 5fdb14446b
14 files changed
+499 -70

No files matched your search

+2 -2
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }
+7 -2
View File
@@ -18,14 +18,19 @@ argh = "0.1.4"
serde = "1.0.120" serde = "1.0.120"
serde_json = "1.0.61" serde_json = "1.0.61"
async-std = { version = "1.9.0", features = ["attributes"] } async-std = { version = "1.9.0", features = ["attributes"] }
tide = "0.15.0" tide = "0.16.0"
fs_extra = "1.2.0" fs_extra = "1.2.0"
cargo_toml = "0.8.1" 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" rjdebounce = "0.2.1"
tempfile = "3.2.0" tempfile = "3.2.0"
html_parser = "0.6.2"
tui = { version = "0.15.0", features = ["crossterm"] }
crossterm = "0.19.0"
tui-template = { git = "https://github.com/jkelleyrtp/tui-builder.git" }
[[bin]] [[bin]]
+19 -7
View File
@@ -1,10 +1,13 @@
use crate::{ use crate::{
cli::BuildOptions, cli::BuildOptions,
config::{Config, ExecutableType}, config::{CrateConfig, ExecutableType},
error::Result, error::{Error, Result},
}; };
use log::{info, warn}; use log::{info, warn};
use std::{io::Write, process::Command}; use std::{
io::{Read, Write},
process::Command,
};
use wasm_bindgen_cli_support::Bindgen; use wasm_bindgen_cli_support::Bindgen;
pub struct BuildConfig {} pub struct BuildConfig {}
@@ -19,7 +22,7 @@ impl Default for BuildConfig {
} }
} }
pub fn build(config: &Config, _build_config: &BuildConfig) -> Result<()> { 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
@@ -28,7 +31,7 @@ pub fn build(config: &Config, _build_config: &BuildConfig) -> Result<()> {
[5] Link up the html page to the wasm module [5] Link up the html page to the wasm module
*/ */
let Config { let CrateConfig {
out_dir, out_dir,
crate_dir, crate_dir,
target_dir, target_dir,
@@ -60,9 +63,16 @@ pub fn build(config: &Config, _build_config: &BuildConfig) -> Result<()> {
}; };
let mut child = cmd.spawn()?; let mut child = cmd.spawn()?;
let _err_code = child.wait()?; let output = child.wait()?;
info!("Build complete!"); if output.success() {
info!("Build complete!");
} else {
log::error!("Build failed!");
let mut reason = String::new();
child.stderr.unwrap().read_to_string(&mut reason)?;
return Err(Error::BuildFailed(reason));
}
// [2] Establish the output directory structure // [2] Establish the output directory structure
let bindgen_outdir = out_dir.join("wasm"); let bindgen_outdir = out_dir.join("wasm");
@@ -129,6 +139,8 @@ fn gen_page(module: &str) -> String {
<meta charset="UTF-8" /> <meta charset="UTF-8" />
</head> </head>
<body> <body>
<div id="dioxusroot">
</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">
import init from "{}"; import init from "{}";
+40 -5
View File
@@ -8,22 +8,24 @@ pub struct LaunchOptions {
} }
/// The various kinds of commands that `wasm-pack` can execute. /// The various kinds of commands that `wasm-pack` can execute.
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand)] #[argh(subcommand)]
pub enum LaunchCommand { pub enum LaunchCommand {
Develop(DevelopOptions), Develop(DevelopOptions),
Build(BuildOptions), Build(BuildOptions),
Translate(TranslateOptions),
Test(TestOptions), Test(TestOptions),
Publish(PublishOptions), Publish(PublishOptions),
Studio(StudioOptions),
} }
/// Publish your yew application to Github Pages, Netlify, or S3 /// Publish your yew application to Github Pages, Netlify, or S3
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "publish")] #[argh(subcommand, name = "publish")]
pub struct PublishOptions {} pub struct PublishOptions {}
/// 🔬 test your yew application! /// 🔬 test your yew application!
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "test")] #[argh(subcommand, name = "test")]
pub struct TestOptions { pub struct TestOptions {
/// an example in the crate /// an example in the crate
@@ -35,7 +37,7 @@ pub struct TestOptions {
#[derive(FromArgs, PartialEq, Debug, Clone)] #[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "build")] #[argh(subcommand, name = "build")]
pub struct BuildOptions { pub struct BuildOptions {
/// an optional direction which is "up" by default /// the directory output
#[argh(option, short = 'o', default = "String::from(\"public\")")] #[argh(option, short = 'o', default = "String::from(\"public\")")]
pub outdir: String, pub outdir: String,
@@ -46,10 +48,18 @@ pub struct BuildOptions {
/// develop in release mode /// develop in release mode
#[argh(switch, short = 'r')] #[argh(switch, short = 'r')]
pub release: bool, pub release: bool,
/// hydrate the `dioxusroot` element with this content
#[argh(option, short = 'h')]
pub hydrate: Option<String>,
/// custom template
#[argh(option, short = 't')]
pub template: Option<String>,
} }
/// 🛠 Start a development server /// 🛠 Start a development server
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "develop")] #[argh(subcommand, name = "develop")]
pub struct DevelopOptions { pub struct DevelopOptions {
/// an example in the crate /// an example in the crate
@@ -59,4 +69,29 @@ pub struct DevelopOptions {
/// develop in release mode /// develop in release mode
#[argh(switch, short = 'r')] #[argh(switch, short = 'r')]
pub release: bool, pub release: bool,
/// hydrate the `dioxusroot` element with this content
#[argh(option, short = 'h')]
pub hydrate: Option<String>,
/// custom template
#[argh(option, short = 't')]
pub template: Option<String>,
} }
/// 🛠 Translate some 3rd party template into rsx
#[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "translate")]
pub struct TranslateOptions {
/// an example in the crate
#[argh(option, short = 'f')]
pub file: Option<String>,
/// an example in the crate
#[argh(option, short = 't')]
pub text: Option<String>,
}
/// 🛠 Translate some 3rd party template into rsx
#[derive(FromArgs, PartialEq, Debug, Clone)]
#[argh(subcommand, name = "studio")]
pub struct StudioOptions {}
+3 -2
View File
@@ -1,11 +1,12 @@
use crate::{ use crate::{
cli::{BuildOptions, DevelopOptions}, cli::{BuildOptions, DevelopOptions},
error::Result, error::Result,
LaunchCommand,
}; };
use std::{io::Write, path::PathBuf, process::Command}; use std::{io::Write, path::PathBuf, process::Command};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct CrateConfig {
pub out_dir: PathBuf, pub out_dir: PathBuf,
pub crate_dir: PathBuf, pub crate_dir: PathBuf,
pub workspace_dir: PathBuf, pub workspace_dir: PathBuf,
@@ -23,7 +24,7 @@ pub enum ExecutableType {
Example(String), Example(String),
} }
impl Config { impl CrateConfig {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let crate_dir = crate::cargo::crate_root()?; let crate_dir = crate::cargo::crate_root()?;
let workspace_dir = crate::cargo::workspace_root()?; let workspace_dir = crate::cargo::workspace_root()?;
+54 -22
View File
@@ -1,4 +1,4 @@
use crate::{builder::BuildConfig, cli::DevelopOptions, config::Config, error::Result}; use crate::{builder::BuildConfig, cli::DevelopOptions, config::CrateConfig, error::Result};
use async_std::prelude::FutureExt; use async_std::prelude::FutureExt;
use async_std::future; use async_std::future;
@@ -7,6 +7,8 @@ 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::Arc;
use std::time::Duration; use std::time::Duration;
pub struct DevelopConfig {} pub struct DevelopConfig {}
@@ -16,15 +18,20 @@ impl Into<DevelopConfig> for DevelopOptions {
} }
} }
pub async fn start(config: &Config, _options: &DevelopConfig) -> Result<()> { type ErrStatus = Arc<AtomicBool>;
pub async fn start(config: &CrateConfig, _options: &DevelopConfig) -> Result<()> {
log::info!("Starting development server 🚀"); log::info!("Starting development server 🚀");
let Config { out_dir, .. } = config; let CrateConfig { out_dir, .. } = config;
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())); let server = async_std::task::spawn(launch_server(out_dir.clone(), is_err.clone()));
let watcher = async_std::task::spawn(watch_directory(config.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),
@@ -34,14 +41,19 @@ pub async fn start(config: &Config, _options: &DevelopConfig) -> Result<()> {
Ok(()) Ok(())
} }
async fn watch_directory(config: Config) -> Result<()> { async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
// Create a channel to receive the events. // Create a channel to receive the events.
let (watcher_tx, watcher_rx) = async_std::channel::bounded(100); let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
// Automatically select the best implementation for your platform. // Automatically select the best implementation for your platform.
// You can also access each implementation directly e.g. INotifyWatcher. // You can also access each implementation directly e.g. INotifyWatcher.
let mut watcher: RecommendedWatcher = Watcher::new(move |res| { let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
<<<<<<< HEAD
async_std::task::block_on(watcher_tx.send(res)); async_std::task::block_on(watcher_tx.send(res));
=======
// send an event
let _ = async_std::task::block_on(watcher_tx.send(res));
>>>>>>> 9451713 (wip: studio upgrades)
}) })
.expect("failed to make watcher"); .expect("failed to make watcher");
@@ -53,44 +65,63 @@ async fn watch_directory(config: Config) -> Result<()> {
.watch(&src_dir.join("src"), RecursiveMode::Recursive) .watch(&src_dir.join("src"), RecursiveMode::Recursive)
.expect("Failed to watch dir"); .expect("Failed to watch dir");
watcher match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
.watch(&src_dir.join("examples"), RecursiveMode::Recursive) Ok(_) => {}
.expect("Failed to watch dir"); Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
}
let build_config = BuildConfig::default(); let build_config = BuildConfig::default();
'run: loop { 'run: loop {
crate::builder::build(&config, &build_config)?; match crate::builder::build(&config, &build_config) {
Ok(_) => {
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
}
Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
};
// Wait for the message with a debounce let mut msg = None;
let _msg = watcher_rx loop {
.recv() let new_msg = watcher_rx.recv().await.unwrap().unwrap();
.join(future::ready(1_usize).delay(Duration::from_millis(2000))) if !watcher_rx.is_empty() {
.await; msg = Some(new_msg);
break;
}
}
info!("File updated, rebuilding app"); info!("File updated, rebuilding app");
} }
Ok(()) Ok(())
} }
async fn launch_server(outdir: PathBuf) -> Result<()> { async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
let _crate_dir = crate::cargo::crate_root()?; let _crate_dir = crate::cargo::crate_root()?;
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())); let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
let p = outdir.display().to_string(); let p = outdir.display().to_string();
app.at("/") app.at("/")
.get(|req: tide::Request<ServerState>| async move { .get(|req: tide::Request<ServerState>| async move {
log::info!("Connected to development server"); log::info!("Connected to development server");
let state = req.state(); let state = req.state();
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
true => Ok(tide::Body::from_string(format!(
include_str!("./err.html"),
err = "_"
))),
false => {
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
}
}
}) })
.serve_dir(p)?; .serve_dir(p)?;
let port = "8080"; let port = "8080";
let serve_addr = format!("0.0.0.0:{}", port); // 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?;
@@ -103,9 +134,10 @@ async fn launch_server(outdir: PathBuf) -> Result<()> {
#[derive(Clone)] #[derive(Clone)]
struct ServerState { struct ServerState {
serv_path: PathBuf, serv_path: PathBuf,
is_err: ErrStatus,
} }
impl ServerState { impl ServerState {
fn new(serv_path: PathBuf) -> Self { fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
Self { serv_path } Self { serv_path, is_err }
} }
} }
+19
View File
@@ -0,0 +1,19 @@
<html>
<head></head>
<body>
<div>
<h1>
Sorry, but building your application failed.
</h1>
<p>
Here's the error:
</p>
<code>
{err}
</code>
</div>
</body>
</html>
+3
View File
@@ -14,6 +14,9 @@ pub enum Error {
#[error("Failed to write error")] #[error("Failed to write error")]
FailedToWrite, FailedToWrite,
#[error("Building project failed")]
BuildFailed(String),
#[error("Failed to write error")] #[error("Failed to write error")]
CargoError(String), CargoError(String),
+21 -8
View File
@@ -1,8 +1,21 @@
pub mod builder; mod builder;
pub mod cargo; mod cargo;
pub mod cli; mod cli;
pub mod config; mod config;
pub mod develop; mod develop;
pub mod error; mod error;
pub mod logging; mod logging;
pub mod watch; 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::*;
-2
View File
@@ -44,6 +44,4 @@ pub fn set_up_logging() {
.chain(std::io::stdout()) .chain(std::io::stdout())
.apply() .apply()
.unwrap(); .unwrap();
debug!("finished setting up logging! yay!");
} }
+4 -20
View File
@@ -1,28 +1,12 @@
use dioxus_studio as diopack; use dioxus_studio::{set_up_logging, LaunchCommand, LaunchOptions};
use dioxus_studio::cli::{LaunchCommand, LaunchOptions};
#[async_std::main] #[async_std::main]
async fn main() -> diopack::error::Result<()> { async fn main() -> dioxus_studio::Result<()> {
diopack::logging::set_up_logging(); set_up_logging();
let opts: LaunchOptions = argh::from_env(); let opts: LaunchOptions = argh::from_env();
let mut config = diopack::config::Config::new()?;
match opts.command { dioxus_studio::Studio::new(opts).start().await?;
LaunchCommand::Build(options) => {
config.with_build_options(&options);
diopack::builder::build(&config, &(options.into()))?;
}
LaunchCommand::Develop(options) => {
config.with_develop_options(&options);
diopack::develop::start(&config, &(options.into())).await?;
}
_ => {
todo!("Command not currently implemented");
}
}
Ok(()) Ok(())
} }
+194
View File
@@ -0,0 +1,194 @@
//! 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::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
symbols,
text::{Span, Spans},
widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle},
widgets::{
Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List,
ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
},
Frame,
};
struct TuiStudio {
cfg: Studio,
hook_idx: usize,
hooks: Vec<Box<dyn Any>>,
}
impl TuiStudio {
fn use_hook<F: 'static>(&mut self, f: impl FnOnce() -> F) -> &mut F {
if self.hook_idx == self.hooks.len() {
self.hooks.push(Box::new(f()));
}
let idx = self.hook_idx;
self.hook_idx += 1;
let hook = self.hooks.get_mut(idx).unwrap();
let r = hook.downcast_mut::<F>().unwrap();
r
}
}
impl TuiApp for TuiStudio {
fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> {
match action {
crossterm::event::Event::Key(_) => {}
crossterm::event::Event::Mouse(_) => {}
crossterm::event::Event::Resize(_, _) => {}
}
Ok(())
}
fn handle_key(&mut self, key: crossterm::event::KeyEvent) {}
fn tick(&mut self) {}
fn should_quit(&self) -> bool {
false
}
fn render<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
self.hook_idx = 0;
// Wrapping block for a group
// Just draw the block and the group on the same area and build the group
// with at least a margin of 1
let size = f.size();
let block = Block::default()
.borders(Borders::ALL)
.title("Main block with round corners")
.border_type(BorderType::Rounded);
f.render_widget(block, size);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(4)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(f.size());
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[0]);
let block = Block::default()
.title(vec![
Span::styled("With", Style::default().fg(Color::Yellow)),
Span::from(" background"),
])
.style(Style::default().bg(Color::Green));
f.render_widget(block, top_chunks[0]);
let block = Block::default().title(Span::styled(
"Styled title",
Style::default()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD),
));
f.render_widget(block, top_chunks[1]);
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);
let block = Block::default().title("With borders").borders(Borders::ALL);
f.render_widget(block, bottom_chunks[0]);
let block = Block::default()
.title("With styled borders and doubled borders")
.border_style(Style::default().fg(Color::Cyan))
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Double);
f.render_widget(block, bottom_chunks[1]);
}
}
impl TuiStudio {
fn render_list<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
let options = [
"Bundle", "Develop",
//
];
}
}
+101
View File
@@ -0,0 +1,101 @@
use std::fmt::{Debug, Display, Formatter};
use anyhow::Result;
use html_parser::{Dom, Node};
pub fn translate_from_html_file(target: &str) -> Result<RsxRenderer> {
use std::fs::File;
use std::io::Read;
let mut file = File::open(target).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Failed to read your file.");
translate_from_html_to_rsx(&contents, true)
}
pub fn translate_from_html_to_rsx(html: &str, as_call: bool) -> Result<RsxRenderer> {
let contents = Dom::parse(html)?;
let renderer = RsxRenderer {
as_call,
dom: contents,
};
Ok(renderer)
}
pub struct RsxRenderer {
dom: Dom,
as_call: bool,
}
impl Display for RsxRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.as_call {
writeln!(f, r##"use dioxus::prelude::*;"##)?;
writeln!(f, r##"const Component: FC<()> = |cx| cx.render(rsx!{{"##)?;
}
for child in &self.dom.children {
render_child(f, child, 1)?;
}
if self.as_call {
write!(f, r##"}});"##)?;
}
Ok(())
}
}
fn render_child(f: &mut Formatter<'_>, child: &Node, il: u32) -> std::fmt::Result {
write_tabs(f, il);
match child {
Node::Text(t) => writeln!(f, "\"{}\"", t)?,
Node::Comment(e) => writeln!(f, "/* {} */", e)?,
Node::Element(el) => {
// 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)?;
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)?;
}
// 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(())
}
+32
View File
@@ -0,0 +1,32 @@
<section class="text-gray-600 body-font">
<div class="container px-5 py-24 mx-auto">
<div class="flex flex-wrap -mx-4 -mb-10 text-center">
<div class="sm:w-1/2 mb-10 px-4">
<div class="rounded-lg h-64 overflow-hidden">
<img alt="content" class="object-cover object-center h-full w-full"
src="https://dummyimage.com/1201x501">
</div>
<h2 class="title-font text-2xl font-medium text-gray-900 mt-6 mb-3">Buy YouTube Videos</h2>
<p class="leading-relaxed text-base">
Williamsburg occupy sustainable snackwave gochujang. Pinterest cornhole brunch, slow-carb neutra
irony.
</p>
<button
class="flex mx-auto mt-6 text-white bg-indigo-500 border-0 py-2 px-5 focus:outline-none hover:bg-indigo-600 rounded">Button</button>
</div>
<div class="sm:w-1/2 mb-10 px-4">
<div class="rounded-lg h-64 overflow-hidden">
<img alt="content" class="object-cover object-center h-full w-full"
src="https://dummyimage.com/1202x502">
</div>
<h2 class="title-font text-2xl font-medium text-gray-900 mt-6 mb-3">The Catalyzer</h2>
<p class="leading-relaxed text-base">
Williamsburg occupy sustainable snackwave gochujang. Pinterest
cornhole brunch, slow-carb neutra irony.
</p>
<button
class="flex mx-auto mt-6 text-white bg-indigo-500 border-0 py-2 px-5 focus:outline-none hover:bg-indigo-600 rounded">Button</button>
</div>
</div>
</div>
</section>