Fix up the tabletop layout
This commit is contained in:
@@ -14,7 +14,6 @@ pub struct ImageProps {
|
||||
#[function_component]
|
||||
pub fn Image(ImageProps { class, url }: &ImageProps) -> Html {
|
||||
let styles = css!(
|
||||
object-fit: cover;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ pub struct PanelProps {
|
||||
#[prop_or(Classes::new())]
|
||||
pub class: Classes,
|
||||
|
||||
#[prop_or_default]
|
||||
pub ref_: NodeRef,
|
||||
|
||||
#[prop_or(None)]
|
||||
pub title: Option<AttrValue>,
|
||||
|
||||
@@ -21,6 +24,7 @@ pub struct PanelProps {
|
||||
pub fn Panel(
|
||||
PanelProps {
|
||||
class,
|
||||
ref_,
|
||||
title,
|
||||
on_click,
|
||||
children,
|
||||
@@ -64,7 +68,7 @@ pub fn Panel(
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class={classes!((*class).clone(), styles, hover_style)} onclick={click_handler}>
|
||||
<div ref={ref_} class={classes!((*class).clone(), styles, hover_style)} onclick={click_handler}>
|
||||
{title_element}
|
||||
{children.clone()}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use glimmer_yew::prelude::*;
|
||||
use gloo_console::log;
|
||||
use stylist::css;
|
||||
use utils::clone;
|
||||
use visions_types::Card;
|
||||
use web_sys::HtmlDivElement;
|
||||
use yew::prelude::*;
|
||||
@@ -42,65 +44,20 @@ pub fn TabletopElement(
|
||||
cards,
|
||||
}: &TabletopElementProperties,
|
||||
) -> Html {
|
||||
let stylesheet = use_stylesheet();
|
||||
|
||||
let tabletop_ref = use_node_ref();
|
||||
let tabletop_height = tabletop_ref
|
||||
.cast::<HtmlDivElement>()
|
||||
.map(|element| element.offset_height())
|
||||
.unwrap_or(0);
|
||||
|
||||
let (image_style, sidebar_height) = if let Some(stylesheet) = stylesheet {
|
||||
let image_style = classes!(css!(
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
));
|
||||
|
||||
let sidebar_height = format!(
|
||||
"100vh - {} - {} - {}px",
|
||||
stylesheet.space_md, stylesheet.space_md, tabletop_height
|
||||
);
|
||||
|
||||
(image_style, sidebar_height)
|
||||
} else {
|
||||
(classes!(), "".to_owned())
|
||||
};
|
||||
|
||||
let image = if let Some(image_url) = tabletop_image {
|
||||
html! { <Image class={image_style} url={image_url.to_owned()} /> }
|
||||
} else {
|
||||
html! { <></> }
|
||||
};
|
||||
|
||||
/*
|
||||
html! {
|
||||
<div class={classes!(class.clone())}>
|
||||
<h1>{title}</h1>
|
||||
{image}
|
||||
<CardsPanel cards={cards.clone()} />
|
||||
</div>
|
||||
}
|
||||
*/
|
||||
|
||||
let background = if let Some(image) = background_image {
|
||||
html! {
|
||||
<BackgroundLayer url={image} />
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
};
|
||||
let title_height: UseStateHandle<usize> = use_state(|| 0);
|
||||
|
||||
html! {
|
||||
<div>
|
||||
{background}
|
||||
<BackgroundLayer url={background_image} />
|
||||
<TabletopLayer
|
||||
ref_={tabletop_ref.clone()}
|
||||
title={title} />
|
||||
title={title}
|
||||
image_url={tabletop_image}
|
||||
title_height={title_height.clone()} />
|
||||
<TopLayer
|
||||
cards={cards.clone()}
|
||||
left_sidebar={left_sidebar.clone()}
|
||||
right_sidebar={right_sidebar.clone()}
|
||||
sidebar_height={sidebar_height} />
|
||||
title_height={*title_height} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -112,74 +69,159 @@ struct BackgroundLayerProps {
|
||||
|
||||
#[function_component]
|
||||
fn BackgroundLayer(BackgroundLayerProps { url }: &BackgroundLayerProps) -> Html {
|
||||
let styles = css!(
|
||||
position: absolute;
|
||||
let layer_style = classes!(css!(
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
filter: brightness(50%);
|
||||
z-index: -1;
|
||||
);
|
||||
z-index: 0;
|
||||
));
|
||||
|
||||
if let Some(ref url) = *url {
|
||||
let image_style = classes!(css!(
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
filter: brightness(50%);
|
||||
object-fit: cover;
|
||||
));
|
||||
|
||||
let image = if let Some(ref url) = *url {
|
||||
html! {
|
||||
<Image class={styles} url={url.to_owned()} />
|
||||
<Image class={image_style} url={url.to_owned()} />
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class={layer_style}>
|
||||
{image}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct TabletopLayerProps {
|
||||
ref_: NodeRef,
|
||||
title: Option<AttrValue>,
|
||||
image_url: Option<AttrValue>,
|
||||
title_height: UseStateHandle<usize>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn TabletopLayer(TabletopLayerProps { ref_, title }: &TabletopLayerProps) -> Html {
|
||||
let stylesheet = use_stylesheet();
|
||||
fn TabletopLayer(
|
||||
TabletopLayerProps {
|
||||
title,
|
||||
image_url,
|
||||
title_height,
|
||||
}: &TabletopLayerProps,
|
||||
) -> Html {
|
||||
let layer_style = classes!(css!(
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
));
|
||||
|
||||
let image_container_style = classes!(css!(
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
));
|
||||
|
||||
let image_style = classes!(css!(
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
));
|
||||
|
||||
let title_ref = use_node_ref();
|
||||
use_effect_with(
|
||||
title_ref.clone(),
|
||||
clone!(title_height, move |title_ref| {
|
||||
let height = title_ref
|
||||
.cast::<HtmlDivElement>()
|
||||
.map(|element| element.offset_height())
|
||||
.unwrap_or(0);
|
||||
title_height.set(height as usize);
|
||||
}),
|
||||
);
|
||||
|
||||
let tabletop_title = if let Some(stylesheet) = use_stylesheet() {
|
||||
stylesheet.typography_heading_lg()
|
||||
} else {
|
||||
classes!()
|
||||
};
|
||||
|
||||
let image = if let Some(image_url) = image_url {
|
||||
html! { <Image class={image_style} url={image_url.to_owned()} /> }
|
||||
} else {
|
||||
html! { <></> }
|
||||
};
|
||||
|
||||
html! {
|
||||
<div ref={ref_.clone()}>
|
||||
<Panel class={tabletop_title}>
|
||||
<div class={layer_style}>
|
||||
<Panel ref_={title_ref.clone()} class={tabletop_title}>
|
||||
{title.clone().unwrap_or(AttrValue::from("unnamed"))}
|
||||
</Panel>
|
||||
<div class={image_container_style}>
|
||||
{image}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct TopLayerProps {
|
||||
#[prop_or_default]
|
||||
class: Classes,
|
||||
cards: Prop<Vec<Card>>,
|
||||
left_sidebar: Option<Html>,
|
||||
right_sidebar: Option<Html>,
|
||||
sidebar_height: String,
|
||||
title_height: usize,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn TopLayer(
|
||||
TopLayerProps {
|
||||
class,
|
||||
cards,
|
||||
left_sidebar,
|
||||
right_sidebar,
|
||||
sidebar_height,
|
||||
title_height,
|
||||
}: &TopLayerProps,
|
||||
) -> Html {
|
||||
let sidebar_style = classes!(css!(height: calc(${sidebar_height});));
|
||||
log!(format!("TopLayer: {}px", title_height));
|
||||
let layer_height = if let Some(stylesheet) = use_stylesheet() {
|
||||
format!(
|
||||
"100vh - {} - {} - {}px",
|
||||
stylesheet.space_md, stylesheet.space_md, title_height
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let layer_style = if let Some(stylesheet) = use_stylesheet() {
|
||||
classes!(css!(
|
||||
position: fixed;
|
||||
top: ${title_height}px;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: calc(${layer_height});
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
visibility: ${if *title_height > 0 { "visible" } else { "hidden" }};
|
||||
))
|
||||
} else {
|
||||
classes!()
|
||||
};
|
||||
|
||||
let left_sidebar = if let Some(sidebar) = left_sidebar {
|
||||
html! {
|
||||
<CollapsibleSidebar class={sidebar_style.clone()} visible={true} side={SidebarSide::Left}>
|
||||
<CollapsibleSidebar visible={true} side={SidebarSide::Left}>
|
||||
{sidebar.clone()}
|
||||
</CollapsibleSidebar>
|
||||
}
|
||||
@@ -189,7 +231,7 @@ fn TopLayer(
|
||||
|
||||
let right_sidebar = if let Some(sidebar) = right_sidebar {
|
||||
html! {
|
||||
<CollapsibleSidebar class={sidebar_style.clone()} visible={true} side={SidebarSide::Right}>
|
||||
<CollapsibleSidebar visible={true} side={SidebarSide::Right}>
|
||||
{sidebar.clone()}
|
||||
</CollapsibleSidebar>
|
||||
}
|
||||
@@ -198,7 +240,7 @@ fn TopLayer(
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<div class={classes!(layer_style)}>
|
||||
{left_sidebar}
|
||||
{right_sidebar}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ use glimmer_yew::prelude::*;
|
||||
use gloo_console::error;
|
||||
use stylist::css;
|
||||
use visions_types::{
|
||||
Card, CardId, GameId, GameMessage, GameOverview, GameRequest, ImageId, Location, SceneId,
|
||||
Card, CardId, GameId, GameMessage, GameOverview, GameRequest, ImageId, Location, Scene, SceneId,
|
||||
};
|
||||
use wasm_sockets::ConnectionStatus;
|
||||
use web_sys::*;
|
||||
@@ -190,171 +190,29 @@ fn View(
|
||||
on_drop_on_gm_inventory,
|
||||
}: &ViewProps,
|
||||
) -> Html {
|
||||
let (scene_title_style, card_title_style) = if let Some(stylesheet) = use_stylesheet() {
|
||||
let scene_title_style = classes!(css!(
|
||||
padding: ${stylesheet.space_md};
|
||||
background-color: ${stylesheet.color_surface_default()};
|
||||
));
|
||||
let card_title_style = classes!(css!(
|
||||
padding: ${stylesheet.space_md};
|
||||
background-color: ${stylesheet.color_surface_default()};
|
||||
));
|
||||
(scene_title_style, card_title_style)
|
||||
} else {
|
||||
(classes!(), classes!())
|
||||
};
|
||||
|
||||
/*
|
||||
let stylesheet = use_stylesheet();
|
||||
|
||||
let (scene_title_style, card_title_style, image_thumbnail_style) =
|
||||
if let Some(stylesheet) = stylesheet {
|
||||
let scene_title_style = classes!(css!(
|
||||
padding: ${stylesheet.space_md};
|
||||
background-color: ${stylesheet.color_surface_default()};
|
||||
));
|
||||
let card_title_style = classes!(css!(
|
||||
padding: ${stylesheet.space_md};
|
||||
background-color: ${stylesheet.color_surface_default()};
|
||||
));
|
||||
let image_style = classes!(stylesheet.thumbnail());
|
||||
(scene_title_style, card_title_style, image_style)
|
||||
} else {
|
||||
(classes!(), classes!(), classes!())
|
||||
};
|
||||
|
||||
let _background_layer = css!(
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
);
|
||||
|
||||
let scene = state
|
||||
.scene
|
||||
.as_ref()
|
||||
.and_then(|id| state.available_scenes.iter().find(|(sid, _)| *sid == *id))
|
||||
.cloned()
|
||||
.unwrap_or((SceneId::from(""), "no-title".to_owned()));
|
||||
|
||||
let available_scenes: Vec<(AttrValue, String)> = state
|
||||
.available_scenes
|
||||
.iter()
|
||||
.map(|(id, title)| (AttrValue::from(id.to_string()), title.clone()))
|
||||
.collect();
|
||||
|
||||
let render_scene_title: Callback<String, Html> = Callback::from(clone!(
|
||||
scene_title_style,
|
||||
move |title| html! { <div class={scene_title_style.clone()}>{title}</div> }
|
||||
));
|
||||
|
||||
let scene_images: Vec<(AttrValue, String)> = state
|
||||
.scene_images
|
||||
.iter()
|
||||
.map(|image| (AttrValue::from(image.id.to_string()), image.url.clone()))
|
||||
.collect();
|
||||
|
||||
let render_image_thumbnail: Callback<String, Html> = Callback::from(clone!(
|
||||
image_thumbnail_style,
|
||||
move |url| html! { <Image class={image_thumbnail_style.clone()} {url} /> }
|
||||
));
|
||||
|
||||
let scene_page = TabPage {
|
||||
label: AttrValue::from("Scenes"),
|
||||
content: html! {
|
||||
<>
|
||||
<List<String>
|
||||
elements={Prop::from(available_scenes)}
|
||||
enabled_item={AttrValue::from(scene.0.to_string())}
|
||||
render_element={render_scene_title}
|
||||
on_select={Callback::from({
|
||||
let on_change_scene = on_change_scene.clone();
|
||||
move |id: AttrValue| on_change_scene.emit(SceneId::from(id.as_str()))
|
||||
})}
|
||||
/>
|
||||
<List<String>
|
||||
elements={Prop::from(scene_images)}
|
||||
render_element={render_image_thumbnail}
|
||||
on_select={Callback::from(
|
||||
clone!(on_change_tabletop, move |image_id: AttrValue| {
|
||||
let image_id = ImageId::from(image_id.to_string());
|
||||
on_change_tabletop.emit(image_id);
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
},
|
||||
};
|
||||
|
||||
let on_select_card: Callback<AttrValue> =
|
||||
Callback::from(clone!(state, move |card_id: AttrValue| {
|
||||
let card_id = CardId::from(card_id.to_string());
|
||||
if let Some(card) = state.cards.iter().find(|card| card.id == card_id) {
|
||||
state.dispatch(ViewStateAction::OpenCard(card.clone()))
|
||||
}
|
||||
}));
|
||||
|
||||
let on_new_card = Callback::from(clone!(state, move |_| {
|
||||
state.dispatch(ViewStateAction::CardNew)
|
||||
}));
|
||||
|
||||
let cards: Vec<(AttrValue, Card)> = state
|
||||
.cards
|
||||
.iter()
|
||||
.filter(|card| card.location == Location::GM)
|
||||
.map(|item| (AttrValue::from(item.id.to_string()), item.clone()))
|
||||
.collect();
|
||||
|
||||
let render_card_title: Callback<Card, Html> =
|
||||
Callback::from(clone!(card_title_style, move |card: Card| html! {
|
||||
<DragSource item={DraggableItem::Card(card.id.clone())}>
|
||||
<div class={card_title_style.clone()}>{card.label()}</div>
|
||||
</DragSource>}));
|
||||
|
||||
let cards_page = TabPage {
|
||||
label: AttrValue::from("Cards"),
|
||||
content: html! {
|
||||
<DropTarget on_drop={on_drop_on_gm_inventory}>
|
||||
<Button on_click={on_new_card}>{"+ New Card"}</Button>
|
||||
<List<Card>
|
||||
elements={Prop::from(cards)}
|
||||
on_select={on_select_card}
|
||||
render_element={render_card_title}
|
||||
/>
|
||||
</DropTarget>
|
||||
},
|
||||
};
|
||||
|
||||
let sidebar_card = match state.sidebar_card {
|
||||
Some(ref card) => html! {
|
||||
<DragSource item={DraggableItem::Card(card.id.clone())}>
|
||||
<CardElement
|
||||
card={card.clone()}
|
||||
on_close={clone!(state, move |_| state.dispatch(ViewStateAction::CloseCard))}
|
||||
on_save={on_save_card} />
|
||||
</DragSource>
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
|
||||
let control_bar = html! {
|
||||
<div>
|
||||
<TabView orientation={Orientation::Vertical} pages={vec![scene_page, cards_page]} />
|
||||
{sidebar_card}
|
||||
</div>
|
||||
};
|
||||
|
||||
let mut characters = state.characters.clone();
|
||||
characters.sort_by(|l, r| l.name().cmp(r.name()));
|
||||
|
||||
let pc_elements = characters
|
||||
.iter()
|
||||
.map(|pc| pc.gm_sidebar())
|
||||
.collect::<Html>();
|
||||
|
||||
let tabletop_title = css!(
|
||||
font-size: 32px;
|
||||
flex-grow: 0;
|
||||
margin: ${*design::SPACING_M};
|
||||
);
|
||||
|
||||
let tabletop_ref: NodeRef = use_node_ref();
|
||||
let tabletop = html! {
|
||||
<div ref={tabletop_ref.clone()}>
|
||||
<Panel class={tabletop_title}>
|
||||
{state.scene_title.clone().unwrap_or("unnamed".to_owned())}
|
||||
</Panel>
|
||||
</div>
|
||||
};
|
||||
|
||||
let tabletop_height = tabletop_ref
|
||||
.cast::<HtmlDivElement>()
|
||||
.map(|element| element.offset_height())
|
||||
.unwrap_or(0);
|
||||
|
||||
let foreground_style = css!(
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@@ -365,16 +223,6 @@ fn View(
|
||||
flex-wrap: wrap;
|
||||
);
|
||||
|
||||
let sidebar_height = format!(
|
||||
"100vh - {} - {} - {}px",
|
||||
*design::SPACING_M,
|
||||
*design::SPACING_M,
|
||||
tabletop_height
|
||||
);
|
||||
let sidebar_style = css!(
|
||||
height: calc(${sidebar_height});
|
||||
);
|
||||
|
||||
let foreground = html! {
|
||||
<div class={classes!(foreground_style)}>
|
||||
<CollapsibleSidebar visible={true} class={sidebar_style.clone()} side={SidebarSide::Left}>
|
||||
@@ -389,17 +237,51 @@ fn View(
|
||||
</CollapsibleSidebar>
|
||||
</div>
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<BackgroundImage url={state.background_url.clone()} />
|
||||
{tabletop}
|
||||
{foreground}
|
||||
</div>
|
||||
}
|
||||
*/
|
||||
|
||||
let on_select_card: Callback<AttrValue> =
|
||||
Callback::from(clone!(state, move |card_id: AttrValue| {
|
||||
let card_id = CardId::from(card_id.to_string());
|
||||
if let Some(card) = state.cards.iter().find(|card| card.id == card_id) {
|
||||
state.dispatch(ViewStateAction::OpenCard(card.clone()))
|
||||
}
|
||||
}));
|
||||
|
||||
let render_scene_title: Callback<String, Html> = Callback::from(clone!(
|
||||
scene_title_style,
|
||||
move |title| html! { <div class={scene_title_style.clone()}>{title}</div> }
|
||||
));
|
||||
|
||||
let render_card_title: Callback<Card, Html> =
|
||||
Callback::from(clone!(card_title_style, move |card: Card| html! {
|
||||
<DragSource item={DraggableItem::Card(card.id.clone())}>
|
||||
<div class={card_title_style.clone()}>{card.label()}</div>
|
||||
</DragSource>
|
||||
}));
|
||||
|
||||
let left_sidebar = html! {
|
||||
<MenuSidebar
|
||||
state={state.clone()}
|
||||
on_change_scene={on_change_scene.clone()}
|
||||
on_change_tabletop={on_change_tabletop}
|
||||
on_select_card={on_select_card.clone()}
|
||||
on_save_card={on_save_card.clone()}
|
||||
on_drop_on_gm_inventory={on_drop_on_gm_inventory.clone()}
|
||||
render_scene_title={render_scene_title.clone()}
|
||||
render_card_title={render_card_title.clone()} />
|
||||
};
|
||||
let right_sidebar = state
|
||||
.characters
|
||||
.iter()
|
||||
.map(|pc| pc.gm_sidebar())
|
||||
.collect::<Html>();
|
||||
html! {
|
||||
<div>{"Placeholder"}</div>
|
||||
<TabletopElement
|
||||
title={state.scene_title.clone()}
|
||||
background_image={state.background_url.clone()}
|
||||
tabletop_image={state.tabletop_image.clone().map(|i| AttrValue::from(i.url))}
|
||||
left_sidebar={Some(left_sidebar)}
|
||||
right_sidebar={Some(right_sidebar)} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,6 +473,128 @@ pub fn GmView<C: Client + Clone + 'static>(GmViewProps { client, game }: &GmView
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct MenuSidebarProps {
|
||||
state: UseReducerHandle<ViewState>,
|
||||
|
||||
on_change_scene: Callback<SceneId>,
|
||||
on_change_tabletop: Callback<ImageId>,
|
||||
on_select_card: Callback<AttrValue>,
|
||||
on_save_card: Callback<(bool, Card)>,
|
||||
on_drop_on_gm_inventory: Callback<DraggableItem>,
|
||||
render_scene_title: Callback<String, Html>,
|
||||
render_card_title: Callback<Card, Html>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn MenuSidebar(
|
||||
MenuSidebarProps {
|
||||
state,
|
||||
on_change_scene,
|
||||
on_change_tabletop,
|
||||
on_select_card,
|
||||
on_save_card,
|
||||
on_drop_on_gm_inventory,
|
||||
render_scene_title,
|
||||
render_card_title,
|
||||
}: &MenuSidebarProps,
|
||||
) -> Html {
|
||||
let thumbnail_style = if let Some(stylesheet) = use_stylesheet() {
|
||||
classes!(stylesheet.thumbnail())
|
||||
} else {
|
||||
classes!()
|
||||
};
|
||||
|
||||
let on_new_card = Callback::from(clone!(state, move |_| {
|
||||
state.dispatch(ViewStateAction::CardNew)
|
||||
}));
|
||||
|
||||
let scene = state
|
||||
.scene
|
||||
.as_ref()
|
||||
.and_then(|id| state.available_scenes.iter().find(|(sid, _)| *sid == *id))
|
||||
.cloned()
|
||||
.unwrap_or((SceneId::from(""), "no-title".to_owned()));
|
||||
|
||||
let available_scenes: Vec<(AttrValue, String)> = state
|
||||
.available_scenes
|
||||
.iter()
|
||||
.map(|(id, title)| (AttrValue::from(id.to_string()), title.clone()))
|
||||
.collect();
|
||||
|
||||
let scene_images: Vec<(AttrValue, String)> = state
|
||||
.scene_images
|
||||
.iter()
|
||||
.map(|image| (AttrValue::from(image.id.to_string()), image.url.clone()))
|
||||
.collect();
|
||||
|
||||
let scene_page = TabPage {
|
||||
label: AttrValue::from("Scenes"),
|
||||
content: html! {
|
||||
<>
|
||||
<List<String>
|
||||
elements={Prop::from(available_scenes)}
|
||||
enabled_item={AttrValue::from(scene.0.to_string())}
|
||||
render_element={render_scene_title}
|
||||
on_select={Callback::from({
|
||||
let on_change_scene = on_change_scene.clone();
|
||||
move |id: AttrValue| on_change_scene.emit(SceneId::from(id.as_str()))
|
||||
})}
|
||||
/>
|
||||
<List<String>
|
||||
elements={Prop::from(scene_images)}
|
||||
render_element={render_thumbnail(thumbnail_style.clone())}
|
||||
on_select={Callback::from(
|
||||
clone!(on_change_tabletop, move |image_id: AttrValue| {
|
||||
let image_id = ImageId::from(image_id.to_string());
|
||||
on_change_tabletop.emit(image_id);
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
},
|
||||
};
|
||||
|
||||
let cards: Vec<(AttrValue, Card)> = state
|
||||
.cards
|
||||
.iter()
|
||||
.filter(|card| card.location == Location::GM)
|
||||
.map(|item| (AttrValue::from(item.id.to_string()), item.clone()))
|
||||
.collect();
|
||||
|
||||
let cards_page = TabPage {
|
||||
label: AttrValue::from("Cards"),
|
||||
content: html! {
|
||||
<DropTarget on_drop={on_drop_on_gm_inventory}>
|
||||
<Button on_click={on_new_card}>{"+ New Card"}</Button>
|
||||
<List<Card>
|
||||
elements={Prop::from(cards)}
|
||||
on_select={on_select_card}
|
||||
render_element={render_card_title}
|
||||
/>
|
||||
</DropTarget>
|
||||
},
|
||||
};
|
||||
|
||||
let sidebar_card = match state.sidebar_card {
|
||||
Some(ref card) => html! {
|
||||
<DragSource item={DraggableItem::Card(card.id.clone())}>
|
||||
<CardElement
|
||||
card={card.clone()}
|
||||
on_close={clone!(state, move |_| state.dispatch(ViewStateAction::CloseCard))}
|
||||
on_save={on_save_card} />
|
||||
</DragSource>
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<TabView orientation={Orientation::Vertical} pages={vec![scene_page, cards_page]} />
|
||||
{sidebar_card}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn on_drop_handler(
|
||||
socket: WebsocketClient,
|
||||
view_state: UseReducerHandle<ViewState>,
|
||||
@@ -615,3 +619,16 @@ fn move_card_to(
|
||||
socket.send(GameRequest::CardUpdate(card));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thumbnail(styles: Classes) -> impl Fn(String) -> Html {
|
||||
clone!(styles, move |url| html! {
|
||||
<Image class={styles.clone()} {url} />
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
let render_image_thumbnail: Callback<String, Html> = Callback::from(clone!(
|
||||
image_thumbnail_style,
|
||||
move |url| html! { <Image class={image_thumbnail_style.clone()} {url} /> }
|
||||
));
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user