fix: scaling, and chat
* Chat ergonomics vastly improved. * Scaling now done through client side settings
This commit is contained in:
parent
98f38c9714
commit
b430c80000
8 changed files with 1564 additions and 439 deletions
|
|
@ -134,7 +134,7 @@ fn SceneDetailView(
|
|||
let (show_delete_confirm, set_show_delete_confirm) = signal(false);
|
||||
let (show_image_modal, set_show_image_modal) = signal(false);
|
||||
|
||||
// Parse dimensions from bounds_wkt
|
||||
// Parse dimensions from bounds_wkt for display
|
||||
let (initial_width, initial_height) = parse_bounds_wkt(&scene.bounds_wkt);
|
||||
|
||||
// Clone scene data for view (to avoid move issues)
|
||||
|
|
@ -145,7 +145,6 @@ fn SceneDetailView(
|
|||
let scene_background_image_path = scene.background_image_path.clone();
|
||||
let scene_background_image_path_for_modal = scene.background_image_path.clone();
|
||||
let scene_background_image_path_for_check = scene.background_image_path.clone();
|
||||
let scene_background_image_path_for_dimensions = scene.background_image_path.clone();
|
||||
let scene_background_color_display = scene.background_color.clone();
|
||||
let scene_created_at = scene.created_at.clone();
|
||||
let scene_updated_at = scene.updated_at.clone();
|
||||
|
|
@ -158,42 +157,32 @@ fn SceneDetailView(
|
|||
);
|
||||
let (background_image_url, set_background_image_url) = signal(String::new());
|
||||
let (clear_background_image, set_clear_background_image) = signal(false);
|
||||
let (infer_dimensions, set_infer_dimensions) = signal(false);
|
||||
let (width, set_width) = signal(initial_width);
|
||||
let (height, set_height) = signal(initial_height);
|
||||
let (dimension_mode, set_dimension_mode) = signal(scene.dimension_mode.clone());
|
||||
let (sort_order, set_sort_order) = signal(scene.sort_order);
|
||||
let (is_entry_point, set_is_entry_point) = signal(scene.is_entry_point);
|
||||
let (is_hidden, set_is_hidden) = signal(scene.is_hidden);
|
||||
|
||||
// UI state for dimension fetching
|
||||
// UI state for dimension detection (read-only display)
|
||||
let (fetching_dimensions, set_fetching_dimensions) = signal(false);
|
||||
let (dimension_message, set_dimension_message) = signal(Option::<(String, bool)>::None);
|
||||
let (detected_dimensions, set_detected_dimensions) = signal(Option::<(u32, u32)>::None);
|
||||
|
||||
let fetch_dimensions = move |_: leptos::ev::MouseEvent| {
|
||||
let url = background_image_url.get();
|
||||
if url.is_empty() {
|
||||
set_dimension_message.set(Some(("Please enter an image URL first".to_string(), false)));
|
||||
return;
|
||||
}
|
||||
|
||||
set_fetching_dimensions.set(true);
|
||||
set_dimension_message.set(None);
|
||||
set_detected_dimensions.set(None);
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
fetch_image_dimensions_client(
|
||||
url,
|
||||
move |w, h| {
|
||||
set_width.set(w as i32);
|
||||
set_height.set(h as i32);
|
||||
set_dimension_message.set(Some((
|
||||
format!("Dimensions: {}x{}", w, h),
|
||||
true,
|
||||
)));
|
||||
set_detected_dimensions.set(Some((w, h)));
|
||||
},
|
||||
move |err| {
|
||||
set_dimension_message.set(Some((err, false)));
|
||||
move |_err| {
|
||||
set_detected_dimensions.set(None);
|
||||
},
|
||||
set_fetching_dimensions,
|
||||
);
|
||||
|
|
@ -210,17 +199,11 @@ fn SceneDetailView(
|
|||
use gloo_net::http::Request;
|
||||
|
||||
let id = scene_id.clone();
|
||||
// Build bounds WKT from width/height
|
||||
let w = width.get();
|
||||
let h = height.get();
|
||||
let bounds_wkt = format!("POLYGON((0 0, {} 0, {} {}, 0 {}, 0 0))", w, w, h, h);
|
||||
|
||||
let mut data = serde_json::json!({
|
||||
"name": name.get(),
|
||||
"description": if description.get().is_empty() { None::<String> } else { Some(description.get()) },
|
||||
"background_color": if background_color.get().is_empty() { None::<String> } else { Some(background_color.get()) },
|
||||
"bounds_wkt": bounds_wkt,
|
||||
"dimension_mode": dimension_mode.get(),
|
||||
"sort_order": sort_order.get(),
|
||||
"is_entry_point": is_entry_point.get(),
|
||||
"is_hidden": is_hidden.get()
|
||||
|
|
@ -230,10 +213,8 @@ fn SceneDetailView(
|
|||
let bg_url = background_image_url.get();
|
||||
if !bg_url.is_empty() {
|
||||
data["background_image_url"] = serde_json::json!(bg_url);
|
||||
// Include infer dimensions flag when uploading new image
|
||||
if infer_dimensions.get() {
|
||||
data["infer_dimensions_from_image"] = serde_json::json!(true);
|
||||
}
|
||||
// Always infer dimensions from new image
|
||||
data["infer_dimensions_from_image"] = serde_json::json!(true);
|
||||
}
|
||||
|
||||
// Include clear flag if set
|
||||
|
|
@ -447,35 +428,19 @@ fn SceneDetailView(
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
// Dimension fetch message
|
||||
<Show when=move || dimension_message.get().is_some()>
|
||||
// Show detected dimensions (read-only feedback for new images)
|
||||
<Show when=move || detected_dimensions.get().is_some()>
|
||||
{move || {
|
||||
let (msg, is_success) = dimension_message.get().unwrap_or_default();
|
||||
let class = if is_success { "alert alert-success" } else { "alert alert-error" };
|
||||
let (w, h) = detected_dimensions.get().unwrap_or((0, 0));
|
||||
view! {
|
||||
<div class=class role="alert" style="margin-bottom: 1rem">
|
||||
<p>{msg}</p>
|
||||
<div class="alert alert-info" role="status" style="margin-bottom: 1rem">
|
||||
<p>"Detected Size: " <strong>{w}</strong> " × " <strong>{h}</strong> " px"</p>
|
||||
<small>"Dimensions will be extracted from the new image when saved"</small>
|
||||
</div>
|
||||
}
|
||||
}}
|
||||
</Show>
|
||||
|
||||
// Infer dimensions checkbox (only shown when new URL is provided)
|
||||
<Show when=move || !background_image_url.get().is_empty()>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-checkbox"
|
||||
prop:checked=move || infer_dimensions.get()
|
||||
on:change=move |ev| set_infer_dimensions.set(event_target_checked(&ev))
|
||||
/>
|
||||
"Infer dimensions from image"
|
||||
</label>
|
||||
<small class="form-help">"If enabled, server will extract dimensions from the image when saving"</small>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{if scene_background_image_path_for_check.is_some() {
|
||||
view! {
|
||||
<div class="checkbox-group">
|
||||
|
|
@ -494,97 +459,6 @@ fn SceneDetailView(
|
|||
view! {}.into_any()
|
||||
}}
|
||||
|
||||
<h3 class="section-title">"Dimensions"</h3>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="width" class="form-label">"Width"</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
min=100
|
||||
max=10000
|
||||
class="form-input"
|
||||
prop:value=move || width.get()
|
||||
on:input=move |ev| {
|
||||
if let Ok(v) = event_target_value(&ev).parse() {
|
||||
set_width.set(v);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="height" class="form-label">"Height"</label>
|
||||
<input
|
||||
type="number"
|
||||
id="height"
|
||||
min=100
|
||||
max=10000
|
||||
class="form-input"
|
||||
prop:value=move || height.get()
|
||||
on:input=move |ev| {
|
||||
if let Ok(v) = event_target_value(&ev).parse() {
|
||||
set_height.set(v);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="dimension_mode" class="form-label">"Dimension Mode"</label>
|
||||
<select
|
||||
id="dimension_mode"
|
||||
class="form-select"
|
||||
on:change=move |ev| set_dimension_mode.set(event_target_value(&ev))
|
||||
>
|
||||
<option value="fixed" selected=move || dimension_mode.get() == "fixed">"Fixed"</option>
|
||||
<option value="viewport" selected=move || dimension_mode.get() == "viewport">"Viewport"</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Button to set dimensions from existing background image
|
||||
{if let Some(ref path) = scene_background_image_path_for_dimensions {
|
||||
let path_for_closure = path.clone();
|
||||
view! {
|
||||
<div class="form-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
disabled=move || fetching_dimensions.get()
|
||||
on:click=move |_| {
|
||||
set_fetching_dimensions.set(true);
|
||||
set_dimension_message.set(None);
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
let path = path_for_closure.clone();
|
||||
fetch_image_dimensions_client(
|
||||
path,
|
||||
move |w, h| {
|
||||
set_width.set(w as i32);
|
||||
set_height.set(h as i32);
|
||||
set_dimension_message.set(Some((
|
||||
format!("Set from image: {}x{}", w, h),
|
||||
true,
|
||||
)));
|
||||
},
|
||||
move |err| {
|
||||
set_dimension_message.set(Some((err, false)));
|
||||
},
|
||||
set_fetching_dimensions,
|
||||
);
|
||||
}
|
||||
}
|
||||
>
|
||||
{move || if fetching_dimensions.get() { "Fetching..." } else { "Set from background image" }}
|
||||
</button>
|
||||
<small class="form-help">"Set dimensions to match the current background image"</small>
|
||||
</div>
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {}.into_any()
|
||||
}}
|
||||
|
||||
<h3 class="section-title">"Options"</h3>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -21,10 +21,6 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
let (description, set_description) = signal(String::new());
|
||||
let (background_color, set_background_color) = signal("#1a1a2e".to_string());
|
||||
let (background_image_url, set_background_image_url) = signal(String::new());
|
||||
let (infer_dimensions, set_infer_dimensions) = signal(false);
|
||||
let (width, set_width) = signal(800i32);
|
||||
let (height, set_height) = signal(600i32);
|
||||
let (dimension_mode, set_dimension_mode) = signal("fixed".to_string());
|
||||
let (sort_order, set_sort_order) = signal(0i32);
|
||||
let (is_entry_point, set_is_entry_point) = signal(false);
|
||||
let (is_hidden, set_is_hidden) = signal(false);
|
||||
|
|
@ -37,7 +33,8 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
let set_created_id = _set_created_id;
|
||||
let (slug_auto, set_slug_auto) = signal(true);
|
||||
let (fetching_dimensions, set_fetching_dimensions) = signal(false);
|
||||
let (dimension_message, set_dimension_message) = signal(Option::<(String, bool)>::None);
|
||||
// Stores detected dimensions for display (read-only)
|
||||
let (detected_dimensions, set_detected_dimensions) = signal(Option::<(u32, u32)>::None);
|
||||
|
||||
let update_name = move |ev: leptos::ev::Event| {
|
||||
let new_name = event_target_value(&ev);
|
||||
|
|
@ -57,27 +54,21 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
let fetch_dimensions = move |_: leptos::ev::MouseEvent| {
|
||||
let url = background_image_url.get();
|
||||
if url.is_empty() {
|
||||
set_dimension_message.set(Some(("Please enter an image URL first".to_string(), false)));
|
||||
return;
|
||||
}
|
||||
|
||||
set_fetching_dimensions.set(true);
|
||||
set_dimension_message.set(None);
|
||||
set_detected_dimensions.set(None);
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
fetch_image_dimensions_client(
|
||||
url,
|
||||
move |w, h| {
|
||||
set_width.set(w as i32);
|
||||
set_height.set(h as i32);
|
||||
set_dimension_message.set(Some((
|
||||
format!("Dimensions: {}x{}", w, h),
|
||||
true,
|
||||
)));
|
||||
set_detected_dimensions.set(Some((w, h)));
|
||||
},
|
||||
move |err| {
|
||||
set_dimension_message.set(Some((err, false)));
|
||||
move |_err| {
|
||||
set_detected_dimensions.set(None);
|
||||
},
|
||||
set_fetching_dimensions,
|
||||
);
|
||||
|
|
@ -96,10 +87,8 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
{
|
||||
use gloo_net::http::Request;
|
||||
|
||||
// Build bounds WKT from width/height
|
||||
let w = width.get();
|
||||
let h = height.get();
|
||||
let bounds_wkt = format!("POLYGON((0 0, {} 0, {} {}, 0 {}, 0 0))", w, w, h, h);
|
||||
// Default bounds - server will override from image if background_image_url is provided
|
||||
let bounds_wkt = "POLYGON((0 0, 800 0, 800 600, 0 600, 0 0))";
|
||||
|
||||
let data = serde_json::json!({
|
||||
"name": name.get(),
|
||||
|
|
@ -107,9 +96,9 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
"description": if description.get().is_empty() { None::<String> } else { Some(description.get()) },
|
||||
"background_color": if background_color.get().is_empty() { None::<String> } else { Some(background_color.get()) },
|
||||
"background_image_url": if background_image_url.get().is_empty() { None::<String> } else { Some(background_image_url.get()) },
|
||||
"infer_dimensions_from_image": infer_dimensions.get(),
|
||||
"infer_dimensions_from_image": true, // Always infer from image
|
||||
"bounds_wkt": bounds_wkt,
|
||||
"dimension_mode": dimension_mode.get(),
|
||||
"dimension_mode": "fixed", // Default to fixed
|
||||
"sort_order": sort_order.get(),
|
||||
"is_entry_point": is_entry_point.get(),
|
||||
"is_hidden": is_hidden.get()
|
||||
|
|
@ -267,80 +256,19 @@ pub fn SceneNewPage() -> impl IntoView {
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
// Dimension fetch message
|
||||
<Show when=move || dimension_message.get().is_some()>
|
||||
// Show detected dimensions (read-only feedback)
|
||||
<Show when=move || detected_dimensions.get().is_some()>
|
||||
{move || {
|
||||
let (msg, is_success) = dimension_message.get().unwrap_or_default();
|
||||
let class = if is_success { "alert alert-success" } else { "alert alert-error" };
|
||||
let (w, h) = detected_dimensions.get().unwrap_or((0, 0));
|
||||
view! {
|
||||
<div class=class role="alert" style="margin-bottom: 1rem">
|
||||
<p>{msg}</p>
|
||||
<div class="alert alert-info" role="status" style="margin-bottom: 1rem">
|
||||
<p>"Detected Size: " <strong>{w}</strong> " × " <strong>{h}</strong> " px"</p>
|
||||
<small>"Dimensions will be extracted from the image when the scene is created"</small>
|
||||
</div>
|
||||
}
|
||||
}}
|
||||
</Show>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-checkbox"
|
||||
prop:checked=move || infer_dimensions.get()
|
||||
on:change=move |ev| set_infer_dimensions.set(event_target_checked(&ev))
|
||||
/>
|
||||
"Infer dimensions from image"
|
||||
</label>
|
||||
<small class="form-help">"If enabled, server will extract dimensions from the image when creating the scene"</small>
|
||||
</div>
|
||||
|
||||
<h3 class="section-title">"Dimensions"</h3>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="width" class="form-label">"Width"</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
min=100
|
||||
max=10000
|
||||
class="form-input"
|
||||
prop:value=move || width.get()
|
||||
on:input=move |ev| {
|
||||
if let Ok(v) = event_target_value(&ev).parse() {
|
||||
set_width.set(v);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="height" class="form-label">"Height"</label>
|
||||
<input
|
||||
type="number"
|
||||
id="height"
|
||||
min=100
|
||||
max=10000
|
||||
class="form-input"
|
||||
prop:value=move || height.get()
|
||||
on:input=move |ev| {
|
||||
if let Ok(v) = event_target_value(&ev).parse() {
|
||||
set_height.set(v);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="dimension_mode" class="form-label">"Dimension Mode"</label>
|
||||
<select
|
||||
id="dimension_mode"
|
||||
class="form-select"
|
||||
on:change=move |ev| set_dimension_mode.set(event_target_value(&ev))
|
||||
>
|
||||
<option value="fixed" selected=move || dimension_mode.get() == "fixed">"Fixed"</option>
|
||||
<option value="viewport" selected=move || dimension_mode.get() == "viewport">"Viewport"</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-title">"Options"</h3>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue