use crate::asset_snapshot::AssetSnapshot;
use crate::resolve::embed_renderer::html_escape_attr;
use crate::resolve::title_params::TitleParams;
const CLASS_EMBED: &str = "moss-embed";
#[allow(unused_variables)]
pub fn synthesize_model_html(
params: &TitleParams,
src: &str,
assets: &AssetSnapshot,
) -> String {
let data_width = match params.get("data-width") {
Some(w) => format!(r#" data-width="{}""#, html_escape_attr(w)),
None => String::new(),
};
let flags = collect_flag_attrs(params);
let style = collect_style_attr(params);
format!(
r#"<model-viewer class="{class}" data-type="3d"{data_width} src="{src}"{flags} touch-action="pan-y" loading="lazy"{style}></model-viewer>"#,
class = CLASS_EMBED,
data_width = data_width,
src = html_escape_attr(src),
flags = flags,
style = style,
)
}
fn collect_flag_attrs(params: &TitleParams) -> String {
const FLAGS: &[(&str, bool)] = &[
("camera-controls", true),
("auto-rotate", true),
("ar", false),
];
let mut out = String::new();
for (flag, default_on) in FLAGS {
let emit = match params.get(flag) {
Some("true") => true,
Some("false") => false,
_ => *default_on,
};
if emit {
out.push(' ');
out.push_str(flag);
}
}
out
}
fn collect_style_attr(params: &TitleParams) -> String {
let w = params.get("width");
let h = params.get("height");
match (w, h) {
(Some(w), Some(h)) => format!(
r#" style="width:{};height:{}""#,
html_escape_attr(w),
html_escape_attr(h),
),
(Some(w), None) => format!(r#" style="width:{}""#, html_escape_attr(w)),
(None, Some(h)) => format!(r#" style="height:{}""#, html_escape_attr(h)),
(None, None) => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_snapshot() -> AssetSnapshot {
AssetSnapshot::new()
}
fn params_with(kvs: &[(&str, &str)]) -> TitleParams {
let mut p = TitleParams::default();
for (k, v) in kvs {
p.insert(*k, *v);
}
p
}
#[test]
fn model_basic_shape() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, "scene.glb", &empty_snapshot());
assert!(out.contains("<model-viewer"), "got: {}", out);
assert!(out.contains(r#"src="scene.glb""#));
}
#[test]
fn model_with_camera_controls() {
let p = params_with(&[("kind", "3d"), ("camera-controls", "true")]);
let out = synthesize_model_html(&p, "scene.glb", &empty_snapshot());
assert!(out.contains("camera-controls"));
}
#[test]
fn model_with_auto_rotate() {
let p = params_with(&[("kind", "3d"), ("auto-rotate", "true")]);
let out = synthesize_model_html(&p, "scene.glb", &empty_snapshot());
assert!(out.contains("auto-rotate"));
}
#[test]
fn model_with_ar() {
let p = params_with(&[("kind", "3d"), ("ar", "true")]);
let out = synthesize_model_html(&p, "scene.glb", &empty_snapshot());
assert!(out.contains(" ar"));
}
#[test]
fn model_escapes_url() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, r#"scene with "spaces".glb"#, &empty_snapshot());
assert!(
!out.contains(r#"src="scene with """#),
"raw quote not escaped, got: {}",
out
);
}
#[test]
fn model_emits_moss_embed_class_and_data_type() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(
out.contains(r#"class="moss-embed" data-type="3d""#),
"got: {}",
out
);
}
#[test]
fn model_emits_touch_action_and_loading() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(out.contains(r#"touch-action="pan-y""#), "got: {}", out);
assert!(out.contains(r#"loading="lazy""#), "got: {}", out);
}
#[test]
fn model_emits_width_style() {
let p = params_with(&[("kind", "3d"), ("width", "400px")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(out.contains(r#"style="width:400px""#), "got: {}", out);
}
#[test]
fn model_emits_width_and_height_style() {
let p = params_with(&[("kind", "3d"), ("width", "400px"), ("height", "400px")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(
out.contains(r#"style="width:400px;height:400px""#),
"got: {}",
out
);
}
#[test]
fn model_emits_data_width_wrapper_attr() {
let p = params_with(&[("kind", "3d"), ("data-width", "wide")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(out.contains(r#"data-width="wide""#), "got: {}", out);
}
#[test]
fn model_suppresses_default_flag_when_param_false() {
let p = params_with(&[("kind", "3d"), ("camera-controls", "false")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(!out.contains("camera-controls"), "got: {}", out);
assert!(out.contains("auto-rotate"), "got: {}", out);
}
#[test]
fn model_defaults_emit_camera_controls_and_auto_rotate() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(out.contains("camera-controls"), "got: {}", out);
assert!(out.contains("auto-rotate"), "got: {}", out);
assert!(!out.contains(" ar"), "ar must stay opt-in, got: {}", out);
}
#[test]
fn model_closes_tag() {
let p = params_with(&[("kind", "3d")]);
let out = synthesize_model_html(&p, "x.glb", &empty_snapshot());
assert!(out.ends_with("></model-viewer>"), "got: {}", out);
}
}