use std::time::Duration;
use dot_ix::{
model::common::{DotSrcAndStyles, GraphvizDotTheme},
rt::IntoGraphvizDotSrc,
web_components::DotSvg,
};
use leptos::*;
const INFO_GRAPH_DEMO: &str = include_str!("info_graph_example.yaml");
#[cfg(target_arch = "wasm32")]
const QUERY_PARAM_SRC: &str = "src";
#[cfg(target_arch = "wasm32")]
fn info_graph_src_init(set_info_graph_src: WriteSignal<String>) {
use lz_str::decompress_from_encoded_uri_component;
use web_sys::{Document, Url};
create_effect(move |_| {
if let Some(window) = web_sys::window() {
let url_search_params = Url::new(&String::from(window.location().to_string()))
.expect("Expected URL to be valid.")
.search_params();
let info_graph_src_initial = url_search_params
.get(QUERY_PARAM_SRC)
.map(|src| {
if src.contains("\n") {
src
} else {
decompress_from_encoded_uri_component(&src).map_or_else(
|| format!("# deserialize src error: invalid data"),
|s| {
String::from_utf16(&s).unwrap_or_else(|_| {
format!("# deserialize src error: invalid data")
})
},
)
}
})
.unwrap_or_else(|| String::from(INFO_GRAPH_DEMO));
set_info_graph_src.set(info_graph_src_initial);
set_timeout(
move || {
let _ = window
.document()
.as_ref()
.and_then(Document::body)
.as_deref()
.map(|element| element.append_with_str_1(""));
},
Duration::from_millis(200),
);
} else {
set_info_graph_src.set(String::from("# Could not extract search params."));
}
});
}
#[component]
pub fn InfoGraph(diagram_only: ReadSignal<bool>) -> impl IntoView {
let (info_graph_src, set_info_graph_src) = create_signal(String::from(INFO_GRAPH_DEMO));
let layout_classes = move || {
if diagram_only.get() {
"flex items-start"
} else {
"flex items-start flex-wrap"
}
};
let textbox_display_classes = move || {
if diagram_only.get() {
"hidden"
} else {
"tabs basis-1/2 grow"
}
};
let (error_text, set_error_text) = create_signal(None::<String>);
let (dot_src, set_dot_src) = create_signal(None::<String>);
let (styles, set_styles) = create_signal(None::<String>);
let dot_src_and_styles = move || {
dot_src
.get()
.zip(styles.get())
.map(|(dot_src, styles)| DotSrcAndStyles { dot_src, styles })
};
#[cfg(target_arch = "wasm32")]
info_graph_src_init(set_info_graph_src);
create_effect(move |_| {
let info_graph_result =
serde_yaml::from_str::<dot_ix::model::info_graph::InfoGraph>(&info_graph_src.get());
let info_graph_result = &info_graph_result;
match info_graph_result {
Ok(info_graph) => {
let DotSrcAndStyles { dot_src, styles } =
IntoGraphvizDotSrc::into(info_graph, &GraphvizDotTheme::default());
set_dot_src.set(Some(dot_src));
set_styles.set(Some(styles));
set_error_text.set(None);
#[cfg(target_arch = "wasm32")]
{
use lz_str::compress_to_encoded_uri_component;
let src_compressed = compress_to_encoded_uri_component(&info_graph_src.get());
if let Some(window) = web_sys::window() {
let url = {
let u = web_sys::Url::new(&String::from(window.location().to_string()))
.expect("Expected URL to be valid.");
u.search_params().set(QUERY_PARAM_SRC, &src_compressed);
u.to_string()
.as_string()
.expect("# Failed to decode src parameter")
};
let _ = window
.history() .and_then(|h| {
h.replace_state_with_url(&"".into(), "".into(), Some(&url))
});
}
}
}
Err(error) => {
set_dot_src.set(None);
set_styles.set(None);
set_error_text.set(Some(format!("{error}")));
}
}
});
view! {
<div class={ layout_classes }>
<div class={ textbox_display_classes }>
<input type="radio" name="tabs" id="tab_info_graph_yml" checked="checked" />
<label for="tab_info_graph_yml">"info_graph.yml"</label>
<div class="tab">
<textarea
id="info_graph_yml"
name="info_graph_yml"
class="
border
border-slate-400
bg-slate-100
font-mono
min-w-full
min-h-full
p-2
rounded
text-xs
"
on:input=leptos_dom::helpers::debounce(Duration::from_millis(400), move |ev| {
let info_graph_src = event_target_value(&ev);
set_info_graph_src.set(info_graph_src);
})
prop:value=info_graph_src />
<br />
<div
class={
move || {
let error_text = error_text.get();
let error_text_empty = error_text
.as_deref()
.map(str::is_empty)
.unwrap_or(true);
if error_text_empty {
"hidden"
} else {
"
border
border-amber-300
bg-gradient-to-b from-amber-100 to-amber-200
rounded
"
}
}
}
>{
move || {
let error_text = error_text.get();
error_text.as_deref()
.unwrap_or("")
.to_string()
}
}</div>
</div>
<input type="radio" name="tabs" id="tab_info_graph_dot" />
<label for="tab_info_graph_dot">"info_graph.dot"</label>
<div class="tab">
<textarea
id="info_graph_dot"
name="info_graph_dot"
class="
border
border-slate-400
bg-slate-100
min-w-full
min-h-full
font-mono
p-2
rounded
text-xs
"
on:input=leptos_dom::helpers::debounce(Duration::from_millis(400), move |ev| {
let dot_src = event_target_value(&ev);
set_dot_src.set(Some(dot_src));
})
prop:value={
move || {
let dot_src = dot_src.get();
dot_src.as_deref()
.unwrap_or("")
.to_string()
}
} />
</div>
</div>
<div class="diagram basis-1/2 grow">
<DotSvg
dot_src_and_styles=dot_src_and_styles
/>
</div>
</div>
}
}