mirror of https://github.com/wayvr-org/wayvr.git
dash-frontend: Skymap catalog fetch
This commit is contained in:
parent
1d8b22d992
commit
7f822b6787
|
|
@ -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="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"/></svg>
|
||||
|
After Width: | Height: | Size: 360 B |
|
|
@ -0,0 +1,11 @@
|
|||
<layout>
|
||||
<elements>
|
||||
<div flex_direction="column">
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" justify_content="center" />
|
||||
</div>
|
||||
<div flex_direction="row" gap="4">
|
||||
<Button id="btn_refresh" tooltip="REFRESH" width="32" height="32" sprite_src_builtin="dashboard/refresh.svg" />
|
||||
<Button id="btn_download_skymaps" height="32" translation="APP_SETTINGS.DOWNLOAD_SKYMAPS" sprite_src_builtin="dashboard/download.svg"/>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
|
||||
"DOUBLE_CURSOR_FIX": "Double cursor fix",
|
||||
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
|
||||
"DOWNLOAD_SKYMAPS": "Download skymaps",
|
||||
"FEATURES": "Features",
|
||||
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
|
||||
"HANDSFREE_POINTER": "Handsfree mode",
|
||||
|
|
@ -72,6 +73,7 @@
|
|||
"LONG_PRESS_DURATION": "Long press duration",
|
||||
"LOOK_AND_FEEL": "Look & Feel",
|
||||
"MISC": "Miscellaneous",
|
||||
"NO_SKYMAPS_FOUND": "No skymaps found",
|
||||
"NOTIFICATIONS_ENABLED": "Enable notifications",
|
||||
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
|
||||
"OPAQUE_BACKGROUND": "Opaque background",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ use wgui::{
|
|||
},
|
||||
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
|
||||
};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
|
||||
use wlx_common::{
|
||||
async_executor::AsyncExecutor, config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::{Frontend, FrontendTask},
|
||||
|
|
@ -78,12 +80,31 @@ enum Task {
|
|||
SetTab(TabNameEnum),
|
||||
}
|
||||
|
||||
struct SettingsMountParams<'a> {
|
||||
mp: &'a mut MacroParams<'a>,
|
||||
globals: &'a WguiGlobals,
|
||||
parent_id: WidgetID,
|
||||
}
|
||||
|
||||
struct SettingsUpdateParams<'a> {
|
||||
layout: &'a mut Layout,
|
||||
executor: &'a AsyncExecutor,
|
||||
}
|
||||
|
||||
trait SettingsTab {
|
||||
fn update(&mut self, _par: SettingsUpdateParams) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TabSettings<T> {
|
||||
pub state: ParserState,
|
||||
|
||||
app_button_ids: Vec<Rc<str>>,
|
||||
context_menu: ContextMenu,
|
||||
|
||||
current_tab: Option<Box<dyn SettingsTab>>,
|
||||
|
||||
tasks: Tasks<Task>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
|
@ -94,6 +115,13 @@ impl<T> Tab<T> for TabSettings<T> {
|
|||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||
if let Some(tab) = &mut self.current_tab {
|
||||
tab.update(SettingsUpdateParams {
|
||||
layout: &mut frontend.layout,
|
||||
executor: &frontend.executor,
|
||||
})?;
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
|
|
@ -507,6 +535,7 @@ impl<T> TabSettings<T> {
|
|||
let root = self.state.get_widget_id("settings_root")?;
|
||||
frontend.layout.remove_children(root);
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
self.current_tab = None;
|
||||
|
||||
let mut mp = MacroParams {
|
||||
layout: &mut frontend.layout,
|
||||
|
|
@ -537,7 +566,11 @@ impl<T> TabSettings<T> {
|
|||
tab_troubleshooting::mount(&mut mp, root)?;
|
||||
}
|
||||
TabNameEnum::Skybox => {
|
||||
tab_skybox::mount(&mut mp, root)?;
|
||||
self.current_tab = Some(Box::new(tab_skybox::State::mount(SettingsMountParams {
|
||||
mp: &mut mp,
|
||||
globals: &globals,
|
||||
parent_id: root,
|
||||
})?));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -572,6 +605,7 @@ impl<T> TabSettings<T> {
|
|||
state: parser_state,
|
||||
marker: PhantomData,
|
||||
context_menu: ContextMenu::default(),
|
||||
current_tab: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
use crate::tab::settings::{
|
||||
SettingType,
|
||||
macros::{MacroParams, options_category, options_checkbox},
|
||||
use crate::{
|
||||
tab::settings::{
|
||||
SettingType, SettingsMountParams, SettingsTab,
|
||||
macros::{options_category, options_checkbox},
|
||||
},
|
||||
views::skymap_list,
|
||||
};
|
||||
use wgui::layout::WidgetID;
|
||||
|
||||
pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> {
|
||||
let c = options_category(mp, parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?;
|
||||
options_checkbox(mp, c, SettingType::UseSkybox)?;
|
||||
options_checkbox(mp, c, SettingType::OpaqueBackground)?;
|
||||
Ok(())
|
||||
pub struct State {
|
||||
skymap_list: skymap_list::View,
|
||||
}
|
||||
|
||||
impl SettingsTab for State {
|
||||
fn update(&mut self, par: super::SettingsUpdateParams) -> anyhow::Result<()> {
|
||||
self.skymap_list.update(par.layout, par.executor)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn mount(par: SettingsMountParams) -> anyhow::Result<Self> {
|
||||
let c = options_category(par.mp, par.parent_id, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?;
|
||||
options_checkbox(par.mp, c, SettingType::UseSkybox)?;
|
||||
options_checkbox(par.mp, c, SettingType::OpaqueBackground)?;
|
||||
|
||||
let skymap_list = skymap_list::View::new(skymap_list::Params {
|
||||
globals: par.globals.clone(),
|
||||
layout: par.mp.layout,
|
||||
parent_id: c,
|
||||
})?;
|
||||
|
||||
Ok(Self { skymap_list })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ pub struct HttpClientResponse {
|
|||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HttpClientResponse {
|
||||
pub fn as_json<T>(self) -> anyhow::Result<T>
|
||||
where
|
||||
T: for<'a> serde::Deserialize<'a>,
|
||||
{
|
||||
let utf8 = str::from_utf8(&self.data)?;
|
||||
Ok(serde_json::from_str::<T>(utf8)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||
log::info!("fetching URL \"{}\"", url);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod cached_fetcher;
|
||||
pub mod http_client;
|
||||
pub mod networking;
|
||||
pub mod pactl_wrapper;
|
||||
pub mod popup_manager;
|
||||
pub mod steam_utils;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
pub mod skymap_catalog;
|
||||
|
||||
// pub const WAYVR_ROOT_URL: &'static str = "https://wayvr.org";
|
||||
pub const WAYVR_SKYMAPS_ROOT: &'static str = "https://wayvr.org/skymaps";
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#![allow(dead_code)] // TODO: Remove later
|
||||
use serde::Deserialize;
|
||||
use wlx_common::async_executor::AsyncExecutor;
|
||||
|
||||
use crate::util::{http_client, networking};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SkymapCatalogEntryFiles {
|
||||
pub size_16k: Option<String>, // "my_skymap_16k.png"
|
||||
pub size_8k: Option<String>, // "my_skymap_8k.png"
|
||||
pub size_4k: Option<String>, // "my_skymap_4k.png"
|
||||
pub size_2k: String, // we should have *at least* this
|
||||
pub preview: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SkymapCatalogEntry {
|
||||
pub uuid: String,
|
||||
pub created_at: String,
|
||||
pub modified_at: String,
|
||||
pub entry_version: u32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub author: String,
|
||||
pub files: SkymapCatalogEntryFiles,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SkymapCatalog {
|
||||
pub version: u32,
|
||||
pub r#type: String,
|
||||
pub entries: Vec<SkymapCatalogEntry>,
|
||||
}
|
||||
|
||||
pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result<SkymapCatalog> {
|
||||
log::info!("Fetching skymap list");
|
||||
|
||||
let res = http_client::get(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
|
||||
let catalog = res.as_json::<SkymapCatalog>()?;
|
||||
|
||||
Ok(catalog)
|
||||
}
|
||||
|
|
@ -4,3 +4,4 @@ pub mod game_cover;
|
|||
pub mod game_launcher;
|
||||
pub mod game_list;
|
||||
pub mod running_games_list;
|
||||
pub mod skymap_list;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
};
|
||||
use wlx_common::{async_executor::AsyncExecutor, config_io};
|
||||
|
||||
use crate::util::{networking, wgui_simple};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
DownloadSkymaps,
|
||||
SetSkymapCatalog(Result<networking::skymap_catalog::SkymapCatalog, String>),
|
||||
Refresh,
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
pub globals: WguiGlobals,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
parser_state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
list_parent: WidgetID,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||
let doc_params = &ParseDocumentParams {
|
||||
globals: params.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/view/skymap_list.xml"),
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?.id;
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_download_skymaps")?,
|
||||
Task::DownloadSkymaps,
|
||||
);
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_refresh")?,
|
||||
Task::Refresh,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
tasks,
|
||||
list_parent,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
break;
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::DownloadSkymaps => {
|
||||
self.download_skymaps(executor)?;
|
||||
}
|
||||
Task::Refresh => {
|
||||
self.refresh(layout)?;
|
||||
}
|
||||
Task::SetSkymapCatalog(skymap_catalog) => {
|
||||
log::info!("{:?}", skymap_catalog);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
|
||||
let res = networking::skymap_catalog::request_catalog(&executor).await;
|
||||
tasks.push(Task::SetSkymapCatalog(res.map_err(|e| format!("{}", e))));
|
||||
}
|
||||
|
||||
fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
let fut = View::skymap_catalog_request_wrapper(self.tasks.clone(), executor.clone());
|
||||
executor.spawn(fut).detach();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
let skymaps_uuids = config_io::get_skymaps_uuids().unwrap_or_default();
|
||||
log::info!("skymap uuids {:?}", skymaps_uuids);
|
||||
|
||||
layout.remove_children(self.list_parent);
|
||||
|
||||
if skymaps_uuids.is_empty() {
|
||||
wgui_simple::create_label(
|
||||
layout,
|
||||
self.list_parent,
|
||||
Translation::from_translation_key("APP_SETTINGS.NO_SKYMAPS_FOUND"),
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,23 @@ pub fn get_config_root() -> PathBuf {
|
|||
CONFIG_ROOT_PATH.clone()
|
||||
}
|
||||
|
||||
pub fn get_skymaps_root() -> PathBuf {
|
||||
get_config_root().join("skymaps")
|
||||
}
|
||||
|
||||
pub fn get_skymaps_uuids() -> anyhow::Result<Vec<String>> {
|
||||
let data = std::fs::read_to_string(get_skymaps_root().join("skymaps.txt"))?;
|
||||
Ok(data.lines().filter(|line| !line.is_empty()).map(String::from).collect())
|
||||
}
|
||||
|
||||
pub fn set_skymaps_uuids(uuids: &[String]) -> anyhow::Result<()> {
|
||||
let skymaps_root = get_skymaps_root();
|
||||
let _ = std::fs::create_dir_all(&skymaps_root);
|
||||
let data = String::from_iter(uuids.iter().map(|uuid| format!("{}\n", uuid)));
|
||||
std::fs::write(skymaps_root.join("skymaps.txt"), data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ConfigRoot {
|
||||
pub fn get_conf_d_path(&self) -> PathBuf {
|
||||
get_config_root().join(match self {
|
||||
|
|
|
|||
Loading…
Reference in New Issue