feat: slug in url if /t is enabled
This commit is contained in:
parent
bf3bd3dff5
commit
29f29358fd
3 changed files with 162 additions and 10 deletions
|
|
@ -259,6 +259,15 @@ pub fn CombinedApp() -> impl IntoView {
|
|||
<Route path=StaticSegment("home") view=HomePage />
|
||||
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
||||
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
||||
<Route
|
||||
path=(
|
||||
StaticSegment("realms"),
|
||||
ParamSegment("slug"),
|
||||
StaticSegment("scenes"),
|
||||
ParamSegment("scene_slug"),
|
||||
)
|
||||
view=RealmPage
|
||||
/>
|
||||
|
||||
// ==========================================
|
||||
// Admin routes (lazy loading)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ pub fn RealmPage() -> impl IntoView {
|
|||
|
||||
let slug = Signal::derive(move || params.read().get("slug").unwrap_or_default());
|
||||
|
||||
// Scene slug from URL (for direct scene navigation)
|
||||
let scene_slug_param = Signal::derive(move || params.read().get("scene_slug"));
|
||||
|
||||
// Channel member state
|
||||
let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
|
||||
let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);
|
||||
|
|
@ -350,10 +353,12 @@ pub fn RealmPage() -> impl IntoView {
|
|||
#[cfg(feature = "hydrate")]
|
||||
let on_teleport_approved = Callback::new(move |info: TeleportInfo| {
|
||||
let scene_id = info.scene_id;
|
||||
let scene_slug = info.scene_slug;
|
||||
let scene_slug = info.scene_slug.clone();
|
||||
let realm_slug = slug.get_untracked();
|
||||
|
||||
// Fetch the new scene data to update the canvas background
|
||||
let scene_slug_for_url = scene_slug.clone();
|
||||
let realm_slug_for_url = realm_slug.clone();
|
||||
spawn_local(async move {
|
||||
use gloo_net::http::Request;
|
||||
let response = Request::get(&format!(
|
||||
|
|
@ -370,6 +375,26 @@ pub fn RealmPage() -> impl IntoView {
|
|||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||
set_scene_dimensions.set((w as f64, h as f64));
|
||||
}
|
||||
|
||||
// Update URL to reflect new scene
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Ok(history) = window.history() {
|
||||
let new_url = if scene.is_entry_point {
|
||||
format!("/realms/{}", realm_slug_for_url)
|
||||
} else {
|
||||
format!(
|
||||
"/realms/{}/scenes/{}",
|
||||
realm_slug_for_url, scene_slug_for_url
|
||||
)
|
||||
};
|
||||
let _ = history.replace_state_with_url(
|
||||
&wasm_bindgen::JsValue::NULL,
|
||||
"",
|
||||
Some(&new_url),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current scene for the viewer
|
||||
set_current_scene.set(Some(scene));
|
||||
}
|
||||
|
|
@ -408,10 +433,118 @@ pub fn RealmPage() -> impl IntoView {
|
|||
// uses scenes directly. Proper channel infrastructure can be added later.
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
// Track whether we've handled initial scene load to prevent double-loading
|
||||
let initial_scene_handled = StoredValue::new_local(false);
|
||||
|
||||
Effect::new(move |_| {
|
||||
// Skip if already handled
|
||||
if initial_scene_handled.get_value() {
|
||||
return;
|
||||
}
|
||||
|
||||
let url_scene_slug = scene_slug_param.get();
|
||||
let has_url_scene = url_scene_slug
|
||||
.as_ref()
|
||||
.is_some_and(|s| !s.is_empty());
|
||||
|
||||
if has_url_scene {
|
||||
// URL has a scene slug - wait for realm data to check if teleport is allowed
|
||||
let Some(realm_with_role) = realm_data.get().flatten() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let realm_slug_val = slug.get();
|
||||
let scene_slug_val = url_scene_slug.unwrap();
|
||||
|
||||
if !realm_with_role.realm.allow_user_teleport {
|
||||
// Teleport disabled - redirect to base realm URL and show error
|
||||
initial_scene_handled.set_value(true);
|
||||
set_error_message.set(Some(
|
||||
"Direct scene access is disabled for this realm".to_string(),
|
||||
));
|
||||
|
||||
// Redirect to base realm URL
|
||||
let navigate = use_navigate();
|
||||
navigate(
|
||||
&format!("/realms/{}", realm_slug_val),
|
||||
leptos_router::NavigateOptions {
|
||||
replace: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Auto-dismiss error after 5 seconds
|
||||
use gloo_timers::callback::Timeout;
|
||||
Timeout::new(5000, move || {
|
||||
set_error_message.set(None);
|
||||
})
|
||||
.forget();
|
||||
|
||||
// Fall back to entry scene
|
||||
if let Some(scene) = entry_scene.get().flatten() {
|
||||
set_channel_id.set(Some(scene.id));
|
||||
set_current_scene.set(Some(scene.clone()));
|
||||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||
set_scene_dimensions.set((w as f64, h as f64));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Teleport allowed - fetch the specific scene
|
||||
initial_scene_handled.set_value(true);
|
||||
spawn_local(async move {
|
||||
use gloo_net::http::Request;
|
||||
let response = Request::get(&format!(
|
||||
"/api/realms/{}/scenes/{}",
|
||||
realm_slug_val, scene_slug_val
|
||||
))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
if let Ok(resp) = response {
|
||||
if resp.ok() {
|
||||
if let Ok(scene) = resp.json::<Scene>().await {
|
||||
set_channel_id.set(Some(scene.id));
|
||||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||
set_scene_dimensions.set((w as f64, h as f64));
|
||||
}
|
||||
set_current_scene.set(Some(scene));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scene not found - show error and fall back to entry scene
|
||||
set_error_message.set(Some(format!(
|
||||
"Scene '{}' not found",
|
||||
scene_slug_val
|
||||
)));
|
||||
|
||||
// Update URL to base realm URL
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Ok(history) = window.history() {
|
||||
let _ = history.replace_state_with_url(
|
||||
&wasm_bindgen::JsValue::NULL,
|
||||
"",
|
||||
Some(&format!("/realms/{}", realm_slug_val)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-dismiss error after 5 seconds
|
||||
use gloo_timers::callback::Timeout;
|
||||
Timeout::new(5000, move || {
|
||||
set_error_message.set(None);
|
||||
})
|
||||
.forget();
|
||||
});
|
||||
} else {
|
||||
// No URL scene slug - use entry scene
|
||||
let Some(scene) = entry_scene.get().flatten() else {
|
||||
return;
|
||||
};
|
||||
initial_scene_handled.set_value(true);
|
||||
set_channel_id.set(Some(scene.id));
|
||||
set_current_scene.set(Some(scene.clone()));
|
||||
|
||||
|
|
@ -419,6 +552,7 @@ pub fn RealmPage() -> impl IntoView {
|
|||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||
set_scene_dimensions.set((w as f64, h as f64));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ pub fn UserRoutes() -> impl IntoView {
|
|||
<Route path=StaticSegment("home") view=HomePage />
|
||||
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
||||
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
||||
<Route
|
||||
path=(
|
||||
StaticSegment("realms"),
|
||||
ParamSegment("slug"),
|
||||
StaticSegment("scenes"),
|
||||
ParamSegment("scene_slug"),
|
||||
)
|
||||
view=RealmPage
|
||||
/>
|
||||
</Routes>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue