move layout code to layout
This commit is contained in:
@@ -12,7 +12,7 @@ wasm-bindgen-futures = "0.4.32"
|
||||
gloo-net = "0.2.3"
|
||||
weblog = "0.3.0"
|
||||
wee_alloc = "0.4.5"
|
||||
ybc = "0.2"
|
||||
ybc = { git = "https://github.com/jheuel/ybc", branch = "yew-0-19-update" }
|
||||
yew = "0.19"
|
||||
yew-hooks = "0.1.56"
|
||||
pathfinding = "3.0.13"
|
||||
|
||||
@@ -6,6 +6,8 @@ use yew::prelude::*;
|
||||
|
||||
use yew::{function_component, html};
|
||||
|
||||
use super::layout::*;
|
||||
|
||||
#[derive(Clone, Debug, Properties, PartialEq)]
|
||||
pub struct GridProps {
|
||||
#[prop_or_default]
|
||||
@@ -14,95 +16,6 @@ pub struct GridProps {
|
||||
pub width: u32,
|
||||
}
|
||||
|
||||
fn get_common_height(row: &[OutputPicture], container_width: u32, margin: u32) -> f32 {
|
||||
let row_width: u32 = container_width - row.len() as u32 * (margin * 2);
|
||||
let total_aspect_ratio: f32 = row
|
||||
.iter()
|
||||
.map(|p| (p.width as f32) / (p.height as f32))
|
||||
.sum();
|
||||
row_width as f32 / total_aspect_ratio
|
||||
}
|
||||
|
||||
fn cost(
|
||||
photos: &[OutputPicture],
|
||||
i: usize,
|
||||
j: usize,
|
||||
width: u32,
|
||||
target_height: u32,
|
||||
margin: u32,
|
||||
) -> u32 {
|
||||
let common_height = get_common_height(&photos[i..j], width, margin);
|
||||
(common_height - target_height as f32).powi(2) as u32
|
||||
}
|
||||
|
||||
fn make_successors(
|
||||
target_height: u32,
|
||||
container_width: u32,
|
||||
photos: &Vec<OutputPicture>,
|
||||
limit_node_search: usize,
|
||||
margin: u32,
|
||||
) -> Vec<Vec<(usize, u32)>> {
|
||||
let mut results = vec![Vec::new(); photos.len()];
|
||||
(0..photos.len()).for_each(|start| {
|
||||
for j in start + 1..photos.len() + 1 {
|
||||
if j - start > limit_node_search {
|
||||
break;
|
||||
}
|
||||
results[start].push((
|
||||
j,
|
||||
cost(photos, start, j, container_width, target_height, margin),
|
||||
));
|
||||
}
|
||||
});
|
||||
results
|
||||
}
|
||||
|
||||
// guesstimate how many neighboring nodes should be searched based on
|
||||
// the aspect ratio of the container with images having an avg AR of 1.5
|
||||
// as the minimum amount of photos per row, plus some nodes
|
||||
fn find_ideal_node_search(target_row_height: u32, container_width: u32) -> usize {
|
||||
let row_aspect_ratio = container_width as f32 / target_row_height as f32;
|
||||
(row_aspect_ratio / 1.5) as usize + 8
|
||||
}
|
||||
|
||||
fn compute_row_layout(
|
||||
container_width: u32,
|
||||
limit_node_search: usize,
|
||||
target_height: u32,
|
||||
margin: u32,
|
||||
photos: &Vec<OutputPicture>,
|
||||
) -> Option<Vec<(u32, u32)>> {
|
||||
console_log!("compute row layout for width: {}", container_width);
|
||||
|
||||
if photos.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let successors = make_successors(
|
||||
target_height,
|
||||
container_width,
|
||||
photos,
|
||||
limit_node_search,
|
||||
margin,
|
||||
);
|
||||
let path = dijkstra(&0, |p| successors[*p].clone(), |p| *p == photos.len());
|
||||
let (path, _cost) = if let Some(p) = path {
|
||||
p
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
};
|
||||
let mut dimensions: Vec<(u32, u32)> = Vec::with_capacity(photos.len());
|
||||
for i in 1..path.len() {
|
||||
let row = &photos[path[i - 1]..path[i]];
|
||||
let height = get_common_height(row, container_width, margin) as u32;
|
||||
(path[i - 1]..path[i]).for_each(|j| {
|
||||
let ratio = photos[j].width as f32 / photos[j].height as f32;
|
||||
dimensions.push(((height as f32 * ratio) as u32, height));
|
||||
});
|
||||
}
|
||||
Some(dimensions)
|
||||
}
|
||||
|
||||
#[function_component(Grid)]
|
||||
pub fn grid(props: &GridProps) -> Html {
|
||||
let target_height = 300;
|
||||
@@ -127,7 +40,6 @@ pub fn grid(props: &GridProps) -> Html {
|
||||
"display: flex;",
|
||||
"flex-wrap: wrap;",
|
||||
"flex-direction: row;",
|
||||
"border: 1px solid black;",
|
||||
)}>
|
||||
{ props.pictures.iter().zip(dimensions).map(|(p, d)|
|
||||
html!{<Picture margin={margin} picture={p.clone()} width={d.0} height={d.1} />}
|
||||
|
||||
90
frontend/src/gallery/layout.rs
Normal file
90
frontend/src/gallery/layout.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use common::OutputPicture;
|
||||
use pathfinding::prelude::dijkstra;
|
||||
use weblog::console_log;
|
||||
|
||||
|
||||
pub fn get_common_height(row: &[OutputPicture], container_width: u32, margin: u32) -> f32 {
|
||||
let row_width: u32 = container_width - row.len() as u32 * (margin * 2);
|
||||
let total_aspect_ratio: f32 = row
|
||||
.iter()
|
||||
.map(|p| (p.width as f32) / (p.height as f32))
|
||||
.sum();
|
||||
row_width as f32 / total_aspect_ratio
|
||||
}
|
||||
|
||||
pub fn cost(
|
||||
photos: &[OutputPicture],
|
||||
i: usize,
|
||||
j: usize,
|
||||
width: u32,
|
||||
target_height: u32,
|
||||
margin: u32,
|
||||
) -> u32 {
|
||||
let common_height = get_common_height(&photos[i..j], width, margin);
|
||||
(common_height - target_height as f32).powi(2) as u32
|
||||
}
|
||||
|
||||
pub fn make_successors(
|
||||
target_height: u32,
|
||||
container_width: u32,
|
||||
photos: &Vec<OutputPicture>,
|
||||
limit_node_search: usize,
|
||||
margin: u32,
|
||||
) -> Vec<Vec<(usize, u32)>> {
|
||||
let mut results = vec![Vec::new(); photos.len()];
|
||||
(0..photos.len()).for_each(|start| {
|
||||
for j in start + 1..photos.len() + 1 {
|
||||
if j - start > limit_node_search {
|
||||
break;
|
||||
}
|
||||
results[start].push((
|
||||
j,
|
||||
cost(photos, start, j, container_width, target_height, margin),
|
||||
));
|
||||
}
|
||||
});
|
||||
results
|
||||
}
|
||||
|
||||
pub fn find_ideal_node_search(target_row_height: u32, container_width: u32) -> usize {
|
||||
let row_aspect_ratio = container_width as f32 / target_row_height as f32;
|
||||
(row_aspect_ratio / 1.5) as usize + 8
|
||||
}
|
||||
|
||||
pub fn compute_row_layout(
|
||||
container_width: u32,
|
||||
limit_node_search: usize,
|
||||
target_height: u32,
|
||||
margin: u32,
|
||||
photos: &Vec<OutputPicture>,
|
||||
) -> Option<Vec<(u32, u32)>> {
|
||||
console_log!("compute row layout for width: {}", container_width);
|
||||
|
||||
if photos.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let successors = make_successors(
|
||||
target_height,
|
||||
container_width,
|
||||
photos,
|
||||
limit_node_search,
|
||||
margin,
|
||||
);
|
||||
let path = dijkstra(&0, |p| successors[*p].clone(), |p| *p == photos.len());
|
||||
let (path, _cost) = if let Some(p) = path {
|
||||
p
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
};
|
||||
let mut dimensions: Vec<(u32, u32)> = Vec::with_capacity(photos.len());
|
||||
for i in 1..path.len() {
|
||||
let row = &photos[path[i - 1]..path[i]];
|
||||
let height = get_common_height(row, container_width, margin) as u32;
|
||||
(path[i - 1]..path[i]).for_each(|j| {
|
||||
let ratio = photos[j].width as f32 / photos[j].height as f32;
|
||||
dimensions.push(((height as f32 * ratio) as u32, height));
|
||||
});
|
||||
}
|
||||
Some(dimensions)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod layout;
|
||||
|
||||
pub mod grid;
|
||||
pub use grid::Grid;
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ use gloo_net::http::Request;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use yew_hooks::prelude::*;
|
||||
|
||||
use ybc::NavbarFixed::Top;
|
||||
use ybc::TileCtx::{Ancestor, Child, Parent};
|
||||
use ybc::TileSize::*;
|
||||
|
||||
use gallery::Grid;
|
||||
|
||||
use yew::prelude::*;
|
||||
@@ -20,12 +24,11 @@ fn app() -> Html {
|
||||
let node = use_node_ref();
|
||||
let size = use_size(node.clone());
|
||||
|
||||
let pictures = use_state(|| vec![]);
|
||||
let pictures = use_state(std::vec::Vec::new);
|
||||
{
|
||||
let pictures = pictures.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
let pictures = pictures.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let url = "/api/pictures/";
|
||||
let fetched_pictures: Vec<OutputPicture> = Request::get(url)
|
||||
@@ -42,12 +45,43 @@ fn app() -> Html {
|
||||
(),
|
||||
);
|
||||
}
|
||||
let navbrand = html! {
|
||||
<ybc::NavbarItem>
|
||||
<ybc::Title classes={classes!("has-text-white")} size={ybc::HeaderSize::Is4}>{"Photos"}</ybc::Title>
|
||||
</ybc::NavbarItem>
|
||||
};
|
||||
let navstart = html! {};
|
||||
let navend = html! {
|
||||
<>
|
||||
<ybc::NavbarItem>
|
||||
<ybc::ButtonAnchor classes={classes!("is-black", "is-outlined")} rel={String::from("noopener noreferrer")} target={String::from("_blank")} href="https://github.com/thedodd/trunk">
|
||||
{"Photos"}
|
||||
</ybc::ButtonAnchor>
|
||||
</ybc::NavbarItem>
|
||||
</>
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<div ref={node} style={"position: 'relative'"} >
|
||||
<Grid pictures={(*pictures).clone()} width={size.0} />
|
||||
</div>
|
||||
<ybc::Navbar
|
||||
fixed={Top}
|
||||
classes={classes!("is-info")}
|
||||
padded={true}
|
||||
{navbrand}
|
||||
{navstart}
|
||||
{navend}
|
||||
/>
|
||||
<ybc::Container fluid=true>
|
||||
<ybc::Tile ctx={Ancestor}>
|
||||
<ybc::Tile ctx={Parent} vertical=true size={Twelve}>
|
||||
<ybc::Tile ctx={Child} classes={classes!("box")}>
|
||||
<div ref={node} style={"position: 'relative'"} >
|
||||
<Grid pictures={(*pictures).clone()} width={size.0} />
|
||||
</div>
|
||||
</ybc::Tile>
|
||||
</ybc::Tile>
|
||||
</ybc::Tile>
|
||||
</ybc::Container>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user