atproto Thingiverse but good
10

Configure Feed

Select the types of activity you want to include in your feed.

some more fixes, plus adjusting initial render

Orual (Jun 29, 2026, 7:06 PM EDT) 10b3b653 4cb45a73

+39 -70
+11 -40
crates/polymodel-renderer-worker/src/worker.rs
··· 50 50 51 51 struct RendererState { 52 52 context: three_d::Context, 53 - mesh: three_d::Gm<three_d::Mesh, three_d::PhysicalMaterial>, 53 + mesh: Option<three_d::Gm<three_d::Mesh, three_d::PhysicalMaterial>>, 54 54 ambient_light: three_d::AmbientLight, 55 55 key_light: three_d::DirectionalLight, 56 56 fill_light: three_d::DirectionalLight, ··· 139 139 140 140 let (ambient_light, key_light, fill_light) = standard_lights(&context); 141 141 142 - let placeholder = build_placeholder_mesh(&context); 143 142 let viewport = three_d::Viewport::new_at_origo(self.pixel_width, self.pixel_height); 144 143 let camera = three_d::Camera::new_perspective( 145 144 viewport, ··· 155 154 self.canvas = Some(canvas); 156 155 self.renderer = Some(RendererState { 157 156 context, 158 - mesh: placeholder, 157 + mesh: None, 159 158 ambient_light, 160 159 key_light, 161 160 fill_light, ··· 164 163 }); 165 164 self.post_event(&RendererEvent::ContextAcquired); 166 165 self.post_event(&RendererEvent::RendererReady); 167 - self.needs_render = true; 168 166 } 169 167 } 170 168 171 - fn build_placeholder_mesh( 172 - context: &three_d::Context, 173 - ) -> three_d::Gm<three_d::Mesh, three_d::PhysicalMaterial> { 174 - let mut mesh = three_d_asset::TriMesh { 175 - positions: three_d_asset::Positions::F32(vec![ 176 - three_d_asset::Vec3::new(0.0, 0.0, 0.18), 177 - three_d_asset::Vec3::new(-0.16, -0.12, -0.08), 178 - three_d_asset::Vec3::new(0.16, -0.12, -0.08), 179 - three_d_asset::Vec3::new(0.0, 0.18, -0.08), 180 - ]), 181 - indices: three_d_asset::Indices::U32(vec![0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2]), 182 - normals: None, 183 - tangents: None, 184 - uvs: None, 185 - colors: None, 186 - }; 187 - mesh.compute_normals(); 188 - 189 - three_d::Gm::new( 190 - three_d::Mesh::new(context, &mesh), 191 - three_d::PhysicalMaterial { 192 - albedo: three_d::Srgba::new_opaque(116, 180, 255), 193 - roughness: 0.72, 194 - metallic: 0.0, 195 - ..Default::default() 196 - }, 197 - ) 198 - } 199 - 200 169 // --------------------------------------------------------------------------- 201 170 // Scene building 202 171 // --------------------------------------------------------------------------- ··· 384 353 renderer.camera.set_viewport(viewport); 385 354 386 355 let screen = three_d::RenderTarget::screen(&renderer.context, st.pixel_width, st.pixel_height); 387 - let _ = screen 388 - .clear(three_d::ClearState::color_and_depth( 389 - 0.035, 0.043, 0.067, 1.0, 1.0, 390 - )) 391 - .render( 356 + let screen = screen.clear(three_d::ClearState::color_and_depth( 357 + 0.035, 0.043, 0.067, 1.0, 1.0, 358 + )); 359 + 360 + if let Some(mesh) = &renderer.mesh { 361 + let _ = screen.render( 392 362 &renderer.camera, 393 - std::slice::from_ref(&renderer.mesh), 363 + std::slice::from_ref(mesh), 394 364 &[ 395 365 &renderer.ambient_light, 396 366 &renderer.key_light, 397 367 &renderer.fill_light, 398 368 ], 399 369 ); 370 + } 400 371 } 401 372 402 373 // --------------------------------------------------------------------------- ··· 437 408 let ph = st.pixel_height; 438 409 if let Some(renderer) = st.renderer.as_mut() { 439 410 let (mesh, camera, control) = build_scene(&renderer.context, &model, pw, ph); 440 - renderer.mesh = mesh; 411 + renderer.mesh = Some(mesh); 441 412 renderer.camera = camera; 442 413 renderer.control = control; 443 414 }
+1 -3
src/browse.rs
··· 6 6 use crate::examples::{HERO_SAMPLE_HANDLES, first_hero_sample_handle}; 7 7 use crate::session::SessionIdentity; 8 8 use crate::thing_card::ThingCard; 9 - use crate::thing_detail::thing_detail_href; 10 9 11 10 /// Cookie set client-side when the logged-out hero band is dismissed; read 12 11 /// server-side so SSR omits the band on return without a hydration flash. ··· 209 208 210 209 #[component] 211 210 fn BrowseThingCard(thing: library::ThingViewBasic) -> Element { 212 - let detail_href = thing_detail_href(&thing.uri); 213 - rsx! { ThingCard { thing, detail_href } } 211 + rsx! { ThingCard { thing } } 214 212 } 215 213 216 214 #[component]
+1
src/main.rs
··· 19 19 mod shell; 20 20 mod thing_card; 21 21 mod thing_detail; 22 + mod thing_routes; 22 23 mod viewer; 23 24 mod viewer_route; 24 25
+1 -3
src/profile.rs
··· 2 2 use crate::Route; 3 3 use crate::session::SessionIdentity; 4 4 use crate::thing_card::ThingCard; 5 - use crate::thing_detail::thing_detail_href; 6 5 use dioxus::prelude::*; 7 6 use jacquard_common::deps::smol_str::SmolStr; 8 7 use jacquard_common::types::string::{Did, Handle, UriValue}; ··· 675 674 #[allow(clippy::useless_format)] 676 675 #[component] 677 676 fn ProfileThingCard(thing: library::ThingViewBasic) -> Element { 678 - let detail_href = thing_detail_href(&thing.uri); 679 - rsx! { ThingCard { thing, detail_href } } 677 + rsx! { ThingCard { thing } } 680 678 } 681 679 682 680 fn list_item_count_label(count: i64) -> String {
+1 -3
src/search.rs
··· 3 3 4 4 use crate::Route; 5 5 use crate::thing_card::ThingCard; 6 - use crate::thing_detail::thing_detail_href; 7 6 8 7 const SEARCH_LIMIT: i64 = 24; 9 8 ··· 219 218 220 219 #[component] 221 220 fn SearchThingCard(thing: library::ThingViewBasic) -> Element { 222 - let detail_href = thing_detail_href(&thing.uri); 223 - rsx! { ThingCard { thing, detail_href } } 221 + rsx! { ThingCard { thing } } 224 222 } 225 223 226 224 fn search_pagination(
+13 -11
src/thing_card.rs
··· 15 15 use crate::author_byline::AuthorByline; 16 16 use crate::client::PolymodelClient; 17 17 use crate::session::SessionIdentity; 18 + use crate::thing_routes::thing_detail_href; 18 19 19 20 #[cfg(test)] 20 21 const COMPLETE_PREVIEW_SVG: &str = "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 480\"%3E%3Crect width=\"640\" height=\"480\" fill=\"%23f4efe4\"/%3E%3Cg stroke=\"%232a6f97\" stroke-width=\"2\" fill=\"none\"%3E%3Cpath d=\"M64 96h512v288H64zM160 144h320v192H160zM256 192h128v96H256z\"/%3E%3Cpath d=\"M64 384 160 336M576 384 480 336M160 144 64 96M480 144 576 96\"/%3E%3C/g%3E%3Cg fill=\"%2366767f\" font-family=\"monospace\" font-size=\"24\"%3E%3Ctext x=\"72\" y=\"438\"%3Eparametric enclosure preview%3C/text%3E%3C/g%3E%3C/svg%3E"; ··· 126 127 127 128 #[allow(clippy::useless_format)] 128 129 #[component] 129 - pub fn ThingCard( 130 - thing: library::ThingViewBasic, 131 - detail_href: Option<String>, 132 - ) -> Element { 130 + pub fn ThingCard(thing: library::ThingViewBasic) -> Element { 133 131 let client = use_context::<PolymodelClient>(); 134 132 let session = use_context::<Signal<SessionIdentity>>(); 135 133 let title = thing_title(&thing); 134 + let detail_href = thing_detail_href(&thing.uri); 136 135 let media = select_card_media(thing.cover.as_deref(), thing.previews.as_deref(), title); 137 136 let stats = stats(&thing); 138 137 let mut like_uri = use_signal(|| thing.viewer.like.clone()); ··· 173 172 div { class: "thing-card-placeholder blueprint-media", role: "img", aria_label: "Blueprint placeholder for missing preview media", 174 173 span { class: "blueprint-axis blueprint-axis-x", "X" } 175 174 span { class: "blueprint-axis blueprint-axis-y", "Y" } 176 - span { class: "blueprint-dimension", "preview pending" } 175 + span { class: "blueprint-dimension", "no preview image" } 177 176 } 178 177 } 179 178 } ··· 184 183 div { class: "thing-card-placeholder blueprint-media", role: "img", aria_label: "Blueprint placeholder for missing preview media", 185 184 span { class: "blueprint-axis blueprint-axis-x", "X" } 186 185 span { class: "blueprint-axis blueprint-axis-y", "Y" } 187 - span { class: "blueprint-dimension", "preview pending" } 186 + span { class: "blueprint-dimension", "no preview image" } 188 187 } 189 188 } 190 189 } ··· 254 253 action_pending.set(false); 255 254 }); 256 255 }, 257 - "♡ {like_label}" 256 + "{like_label}" 258 257 } 259 258 button { 260 259 class: "thing-card-action", ··· 292 291 action_pending.set(false); 293 292 }); 294 293 }, 295 - "☆ {save_label}" 294 + "{save_label}" 296 295 } 297 296 } else { 298 - a { class: "thing-card-action", href: "/oauth/start", aria_label: "Sign in to like {title}", "♡ Like" } 299 - a { class: "thing-card-action", href: "/oauth/start", aria_label: "Sign in to save {title}", "☆ Save" } 297 + a { class: "thing-card-action", href: "/oauth/start", aria_label: "Sign in to like {title}", "Like" } 298 + a { class: "thing-card-action", href: "/oauth/start", aria_label: "Sign in to save {title}", "Save" } 300 299 } 301 300 if let Some(error) = action_error.read().as_ref() { 302 301 span { class: "thing-card-action-error", role: "status", "{error}" } ··· 480 479 thing_title(&view), 481 480 ) 482 481 .unwrap(); 483 - assert_eq!(view.author.display_name.as_ref().map(AsRef::as_ref), Some("Maker Name")); 482 + assert_eq!( 483 + view.author.display_name.as_ref().map(AsRef::as_ref), 484 + Some("Maker Name") 485 + ); 484 486 assert_eq!(view.author.handle.as_ref(), "maker.tools"); 485 487 assert_eq!(media.url, COMPLETE_PREVIEW_SVG); 486 488 assert_eq!(media.alt, "Cover alt");
-10
src/thing_detail.rs
··· 69 69 } 70 70 } 71 71 72 - pub(crate) fn thing_detail_href(uri: &AtUri) -> Option<String> { 73 - let path = uri.path()?; 74 - if path.collection.as_str() != "space.polymodel.library.thing" { 75 - return None; 76 - } 77 - let repo = uri.authority(); 78 - let rkey = path.rkey?; 79 - Some(format!("/{}/thing/{}", repo.as_str(), rkey.as_ref())) 80 - } 81 - 82 72 #[cfg_attr(not(feature = "server"), allow(dead_code))] 83 73 fn part_download_href(uri: &AtUri) -> Option<String> { 84 74 let path = uri.path()?;
+11
src/thing_routes.rs
··· 1 + use jacquard_common::types::string::AtUri; 2 + 3 + pub(crate) fn thing_detail_href(uri: &AtUri) -> Option<String> { 4 + let path = uri.path()?; 5 + if path.collection.as_str() != "space.polymodel.library.thing" { 6 + return None; 7 + } 8 + let repo = uri.authority(); 9 + let rkey = path.rkey?; 10 + Some(format!("/{}/thing/{}", repo.as_str(), rkey.as_ref())) 11 + }