mirror of https://github.com/wayvr-org/wayvr.git
Supporters list
This commit is contained in:
parent
cae2d7c7e2
commit
c85d96c4d9
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M14.94 19.5L12 17.77L9.06 19.5l.78-3.34l-2.59-2.24l3.41-.29L12 10.5l1.34 3.13l3.41.29l-2.59 2.24M20 2H4v2l4.86 3.64a8 8 0 1 0 6.28 0L20 4m-2 11a6 6 0 1 1-7.18-5.88a5.9 5.9 0 0 1 2.36 0A6 6 0 0 1 18 15m-5.37-8h-1.26l-4-3h9.34Z"/></svg>
|
||||
|
After Width: | Height: | Size: 470 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M9.175 10.825Q8 9.65 8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12t-2.825-1.175M4 20v-2.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2V20z"/></svg>
|
||||
|
After Width: | Height: | Size: 442 B |
|
|
@ -1,5 +1,5 @@
|
|||
<layout>
|
||||
<template name="Separator">
|
||||
<rectangle width="100%" min_height="2" color="#FFFFFF10" />
|
||||
<rectangle width="100%" min_height="2" color="outline" />
|
||||
</template>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -1,47 +1,65 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
<include src="../t_separator.xml" />
|
||||
|
||||
<template name="TierCell">
|
||||
<div gap="8" align_items="center">
|
||||
<sprite width="28" height="28" src_builtin="dashboard/medal.svg" color="${color}"/>
|
||||
<label text="${text}" weight="bold" size="18" color="${color}" shadow="shadow"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="SupporterCell">
|
||||
<rectangle padding="8" color="background(transparent)" round="8" border="2" border_color="outline" gap="8" align_items="center" flex_direction="row" >
|
||||
<sprite width="24" height="24" src_builtin="dashboard/person.svg"/>
|
||||
<label text="${username}" weight="bold"/>
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<!-- icon, nickname -->
|
||||
<template name="Avatar">
|
||||
<div flex_direction="column" gap="12" align_items="center">
|
||||
<image src_builtin="${icon}" width="72" height="72" round="100%" position="relative">
|
||||
<rectangle position="absolute" margin_left="-2" margin_top="-2" width="76" height="76" color="highlight" round="100%"/>
|
||||
<image src_builtin="${icon}" width="64" height="64" round="100%" position="relative">
|
||||
<rectangle position="absolute" margin_left="-2" margin_top="-2" width="68" height="68" color="highlight" round="100%"/>
|
||||
</image>
|
||||
<label size="14" text="${nickname}"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<macro name="half"
|
||||
width="50%" gap="16" flex_direction="column" overflow_y="scroll"
|
||||
align_items="center"/>
|
||||
|
||||
<elements>
|
||||
<div position="absolute" width="100%" height="100%" padding="4">
|
||||
<!-- <rectangle width="100%" height="100%" color="#000000" round="6"/> -->
|
||||
</div>
|
||||
<div margin="32" flex_direction="column" align_items="center" justify_content="center" gap="16" height="100%">
|
||||
<div gap="16" align_items="center" width="100%">
|
||||
<sprite color="#FFAAAA" src_builtin="dashboard/donate.svg" min_width="64" min_height="64"/>
|
||||
<label wrap="1" size="20" translation="DONATE.DESCRIPTION" color="on_background"/>
|
||||
<div flex_direction="row" width="100%" min_height="100%" gap="16">
|
||||
<div macro="half">
|
||||
<sprite color="#FFAAAA" src_builtin="dashboard/donate.svg" min_width="48" min_height="48"/>
|
||||
<div gap="16" align_items="center" width="100%">
|
||||
<label align="justified" wrap="1" size="18" translation="DONATE.DESCRIPTION" color="on_background"/>
|
||||
</div>
|
||||
|
||||
<div flex_direction="column" gap="8" align_items="center">
|
||||
<Button id="btn_donate" tooltip="DONATE.BUTTON_TOOLTIP" align_self="baseline">
|
||||
<image src_builtin="dashboard/opencollective_logo.svg" width="200" height="32" margin="8" margin_left="16" margin_right="16"/>
|
||||
</Button>
|
||||
<label text="opencollective.com/wayvr-org"/>
|
||||
</div>
|
||||
|
||||
<label weight="bold" size="20" translation="DONATE.THANK_YOU_WAYVR" color="on_background"/>
|
||||
|
||||
<div gap="32">
|
||||
<Avatar icon="dashboard/avatars/galister.png" nickname="galister"/>
|
||||
<Avatar icon="dashboard/avatars/oo8dev.png" nickname="oo8dev"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div flex_direction="column" gap="8" align_items="center" margin_top="16">
|
||||
<Button id="btn_donate" tooltip="DONATE.BUTTON_TOOLTIP" align_self="baseline">
|
||||
<image src_builtin="dashboard/opencollective_logo.svg" width="252" height="42" margin="8" margin_left="16" margin_right="16"/>
|
||||
</Button>
|
||||
<label text="opencollective.com/wayvr-org"/>
|
||||
<!-- vertical separator -->
|
||||
<rectangle height="100%" min_width="2" color="outline"/>
|
||||
<div macro="half">
|
||||
<div gap="8" align_items="center">
|
||||
<label translation="DONATE.CURRENT_SUPPORTERS" size="18" weight="bold" />
|
||||
<Button round="100%" text="?" tooltip_str="In last 30 days; the list is updated each day" width="24" height="24"/>
|
||||
</div>
|
||||
<div id="current_supporters" flex_direction="column" gap="8" width="100%"/>
|
||||
</div>
|
||||
|
||||
<label weight="bold" size="24" translation="DONATE.THANK_YOU_WAYVR" color="on_background"/>
|
||||
|
||||
<div gap="32" margin_top="16">
|
||||
<Avatar icon="dashboard/avatars/galister.png" nickname="galister"/>
|
||||
<Avatar icon="dashboard/avatars/oo8dev.png" nickname="oo8dev"/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<rectangle color="#FFFFFF33" width="100%" height="2"/>
|
||||
|
||||
<rectangle macro="group_box" align_self="start" width="100%">
|
||||
<label weight="bold" size="18" translation="DONATE.CURRENT_SUPPORTERS"/>
|
||||
</rectangle>
|
||||
-->
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -4,19 +4,23 @@ use wgui::{
|
|||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
layout::WidgetID,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
|
||||
taffy::{self, style_helpers::length},
|
||||
task::Tasks,
|
||||
widget::div::WidgetDiv,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::{Frontend, FrontendTask},
|
||||
tab::{Tab, TabType},
|
||||
util::cached_fetcher,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum Task {}
|
||||
enum Task {
|
||||
SetSupporters(cached_fetcher::Supporters),
|
||||
}
|
||||
|
||||
pub struct TabDonate<T> {
|
||||
#[allow(dead_code)]
|
||||
|
|
@ -24,6 +28,8 @@ pub struct TabDonate<T> {
|
|||
marker: PhantomData<T>,
|
||||
#[allow(dead_code)]
|
||||
tasks: Tasks<Task>,
|
||||
|
||||
id_current_supporters: WidgetID,
|
||||
}
|
||||
|
||||
impl<T> Tab<T> for TabDonate<T> {
|
||||
|
|
@ -31,10 +37,14 @@ impl<T> Tab<T> for TabDonate<T> {
|
|||
TabType::Donate
|
||||
}
|
||||
|
||||
fn update(&mut self, _frontend: &mut Frontend<T>, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
|
||||
// for task in self.tasks.drain() {
|
||||
// match task {}
|
||||
// }
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
|
||||
while !self.tasks.is_empty() {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::SetSupporters(supporters) => self.set_supporters(&mut frontend.layout, supporters)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -48,9 +58,76 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn request_supporters(tasks: Tasks<Task>) {
|
||||
if let Some(supporters) = cached_fetcher::request_supporters().await {
|
||||
tasks.push(Task::SetSupporters(supporters))
|
||||
}
|
||||
}
|
||||
|
||||
fn tier_pretty_print(tier: &str) -> String {
|
||||
format!("{} Tier", wlx_common::locale::capitalize_string(tier))
|
||||
}
|
||||
|
||||
fn tier_color(tier: &str) -> &'static str {
|
||||
match tier {
|
||||
"platinum" => "#aaffff",
|
||||
"gold" => "#ffffaa",
|
||||
"silver" => "#cccccc",
|
||||
"bronze" => "#ffaa66",
|
||||
_ => "on_background",
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TabDonate<T> {
|
||||
fn set_supporters(&mut self, layout: &mut Layout, supporters: cached_fetcher::Supporters) -> anyhow::Result<()> {
|
||||
let globals = layout.state.globals.clone();
|
||||
layout.remove_children(self.id_current_supporters);
|
||||
|
||||
let mut current_tier = "";
|
||||
let mut tier_parent = WidgetID::default();
|
||||
|
||||
for supporter in &supporters.supporters {
|
||||
if supporter.tier != current_tier {
|
||||
current_tier = &supporter.tier;
|
||||
|
||||
let mut params = TemplateParams::new();
|
||||
params.insert_str("text", tier_pretty_print(&supporter.tier));
|
||||
params.insert("color", tier_color(&supporter.tier));
|
||||
self.state.realize_template(
|
||||
&doc_params(&globals),
|
||||
"TierCell",
|
||||
layout,
|
||||
self.id_current_supporters,
|
||||
params,
|
||||
)?;
|
||||
|
||||
tier_parent = layout
|
||||
.add_child(
|
||||
self.id_current_supporters,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
gap: length(8.0_f32),
|
||||
flex_wrap: taffy::FlexWrap::Wrap,
|
||||
..Default::default()
|
||||
},
|
||||
)?
|
||||
.0
|
||||
.id;
|
||||
}
|
||||
|
||||
let mut params = TemplateParams::new();
|
||||
params.insert("username", &supporter.username);
|
||||
self
|
||||
.state
|
||||
.realize_template(&doc_params(&globals), "SupporterCell", layout, tier_parent, params)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, _data: &mut T) -> anyhow::Result<Self> {
|
||||
let state = wgui::parser::parse_from_assets(&doc_params(&frontend.globals), &mut frontend.layout, parent_id)?;
|
||||
let id_current_supporters = state.get_widget_id("current_supporters")?;
|
||||
|
||||
frontend.tasks.handle_button(
|
||||
&state.fetch_component_as::<ComponentButton>("btn_donate")?,
|
||||
|
|
@ -59,10 +136,13 @@ impl<T> TabDonate<T> {
|
|||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
frontend.executor.spawn(request_supporters(tasks.clone())).detach();
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
marker: PhantomData,
|
||||
tasks,
|
||||
id_current_supporters,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,7 @@ impl<T> Tab<T> for TabHome<T> {
|
|||
}
|
||||
|
||||
fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, config: &GeneralConfig) {
|
||||
let mut username = various::get_username();
|
||||
// first character as uppercase
|
||||
if let Some(first) = username.chars().next() {
|
||||
let first = first.to_uppercase().to_string();
|
||||
username.replace_range(0..1, &first);
|
||||
}
|
||||
let username = wlx_common::locale::capitalize_string(&various::get_username());
|
||||
|
||||
let translated = if !config.hide_username {
|
||||
common.i18n().translate_and_replace("HELLO_USER", ("{USER}", &username))
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub enum TabType {
|
|||
impl TabType {
|
||||
pub fn get_preferred_padding(&self) -> f32 {
|
||||
match self {
|
||||
TabType::Welcome | TabType::Donate => 0.0,
|
||||
TabType::Welcome => 0.0,
|
||||
_ => 16.0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,73 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::util::{networking::http_client, steam_utils::AppID};
|
||||
use anyhow::Context;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wlx_common::cache_dir;
|
||||
|
||||
fn get_unix_timestamp() -> u64 {
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Supporter {
|
||||
pub username: String,
|
||||
pub date: String,
|
||||
pub tier: String,
|
||||
pub contribution_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Supporters {
|
||||
pub time_window_days: u32,
|
||||
pub supporters: Vec<Supporter>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SupportersFile {
|
||||
fetch_timestamp: u64,
|
||||
supporters: Option<Supporters>, // empty in case of server/network failure
|
||||
}
|
||||
|
||||
pub async fn request_supporters() -> Option<Supporters> {
|
||||
let cache_file_path = "supporters_v1.json";
|
||||
const CACHE_DURATION_SECS: u64 = 7200;
|
||||
let current_timestamp = get_unix_timestamp();
|
||||
|
||||
if let Some(data) = cache_dir::get_data(cache_file_path).await
|
||||
&& let Ok(string) = str::from_utf8(&data)
|
||||
&& let Ok(file) = serde_json::from_str::<SupportersFile>(string)
|
||||
&& file.fetch_timestamp + CACHE_DURATION_SECS > current_timestamp
|
||||
{
|
||||
return file.supporters;
|
||||
}
|
||||
|
||||
// perform request
|
||||
let mut supporters_file = SupportersFile {
|
||||
fetch_timestamp: current_timestamp,
|
||||
supporters: None,
|
||||
};
|
||||
|
||||
let url = "https://wayvr.org/files/supporters_v1.json";
|
||||
if let Ok(res) = http_client::get_simple(url).await
|
||||
&& let Ok(supporters) = res.into_json::<Supporters>()
|
||||
{
|
||||
supporters_file.supporters = Some(supporters);
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&supporters_file).unwrap() /* safe */;
|
||||
|
||||
cache_dir::set_data(cache_file_path, json.as_bytes()).await.ok()?;
|
||||
|
||||
supporters_file.supporters
|
||||
}
|
||||
|
||||
pub struct CoverArt {
|
||||
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
||||
pub compressed_image_data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub async fn request_image(app_id: AppID) -> anyhow::Result<CoverArt> {
|
||||
pub async fn request_cover_art(app_id: AppID) -> anyhow::Result<CoverArt> {
|
||||
let cache_file_path = format!("cover_arts/{}.bin", app_id);
|
||||
|
||||
// check if file already exists in cache directory
|
||||
|
|
|
|||
|
|
@ -46,14 +46,7 @@ impl BindingsDropdown for XrInputSubpathKind {
|
|||
self
|
||||
.get_str("Translation")
|
||||
.map(Translation::from_translation_key)
|
||||
.unwrap_or_else(|| {
|
||||
let mut chars = self.as_ref().chars();
|
||||
let capitalized = match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
|
||||
};
|
||||
Translation::from_raw_text(&capitalized)
|
||||
})
|
||||
.unwrap_or_else(|| Translation::from_raw_text(&wlx_common::locale::capitalize_string(self.as_ref())))
|
||||
}
|
||||
fn action_str(&self, action: &str, side: XrInputSide) -> Rc<str> {
|
||||
let value = self.as_ref();
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const GAME_COVER_SIZE_Y: f32 = 210.0;
|
|||
|
||||
impl View {
|
||||
async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box<dyn FnOnce(CoverArt)>) {
|
||||
let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
|
||||
let cover_art = match cached_fetcher::request_cover_art(manifest.app_id.clone()).await {
|
||||
Ok(cover_art) => cover_art,
|
||||
Err(e) => {
|
||||
log::error!("request_cover_image failed: {:?}", e);
|
||||
|
|
@ -75,7 +75,7 @@ impl View {
|
|||
|
||||
fn mount_image(&self, layout: &mut Layout, glyph: &CustomGlyphData) -> anyhow::Result<()> {
|
||||
let image = WidgetImage::create(WidgetImageParams {
|
||||
round: WLength::Units(10.0),
|
||||
round: WLength::Units(6.0),
|
||||
glyph_data: Some(glyph.clone()),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
@ -173,7 +173,7 @@ impl View {
|
|||
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0).into()),
|
||||
border_color: Some(BORDER_COLOR_DEFAULT.into()),
|
||||
hover_border_color: Some(BORDER_COLOR_HOVERED.into()),
|
||||
round: WLength::Units(12.0),
|
||||
round: WLength::Units(8.0),
|
||||
border: 2.0,
|
||||
tooltip: Some(TooltipInfo {
|
||||
side: TooltipSide::Bottom,
|
||||
|
|
@ -209,11 +209,11 @@ impl View {
|
|||
},
|
||||
)?;
|
||||
|
||||
let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
|
||||
let rect_gradient = |color: drawing::Color, color2: drawing::Color, round: f32| {
|
||||
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
|
||||
color: color.into(),
|
||||
color2: color2.into(),
|
||||
round: WLength::Units(12.0),
|
||||
round: WLength::Units(round),
|
||||
gradient: GradientMode::Vertical,
|
||||
..Default::default()
|
||||
})
|
||||
|
|
@ -234,7 +234,8 @@ impl View {
|
|||
widget_button.id,
|
||||
rect_gradient(
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.2),
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.02),
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.01),
|
||||
8.0,
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.05),
|
||||
)?;
|
||||
|
|
@ -248,6 +249,7 @@ impl View {
|
|||
rect_gradient(
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.15),
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.0),
|
||||
8.0,
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.5),
|
||||
)?;
|
||||
|
|
@ -258,6 +260,7 @@ impl View {
|
|||
rect_gradient(
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
|
||||
8.0,
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::END, 0.5),
|
||||
)?;
|
||||
|
|
@ -268,6 +271,7 @@ impl View {
|
|||
rect_gradient(
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.1),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.9),
|
||||
8.0,
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::END, 0.05),
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ static DEFAULT: &WguiColorPalette = &WguiColorPalette {
|
|||
hex("#ffebf5"), // OnDanger
|
||||
hex("#002e43"), // Background
|
||||
hex("#e4f5f6"), // OnBackground
|
||||
hex("#0c5170"), // OackgroundVariant
|
||||
hex("#0c5170"), // OnBackgroundVariant
|
||||
hex("#b5cacc"), // OnBackgroundVariant
|
||||
hex("#00131c"), // BackgroundContrast
|
||||
hex("#e4edf6"), // OnBackgroundContrast
|
||||
|
|
|
|||
|
|
@ -89,3 +89,11 @@ impl WayVRLangProvider {
|
|||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capitalize_string(s: &str) -> String {
|
||||
let mut chars = s.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue