From 622f1615608e4eb11cf0a329b54a3c197701fab0 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 27 Jan 2024 15:05:41 -0500 Subject: [PATCH] Add minimal store UI functionality --- backend/src/api/handler.rs | 19 ++- backend/src/api/web.rs | 145 ++++++++++++++++++--- backend/src/main.rs | 8 +- backend/src/persist/file.rs | 4 +- backend/src/settings/detect/auto_detect.rs | 3 + backend/src/settings/driver.rs | 7 +- backend/src/settings/general.rs | 27 ++-- backend/src/settings/traits.rs | 4 + src/backend.ts | 4 + src/components/battery.tsx | 2 +- src/consts.ts | 7 + src/index.tsx | 63 ++++++++- src/store/page.tsx | 48 +++++++ 13 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 src/store/page.tsx diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs index c49d70f..62c433f 100644 --- a/backend/src/api/handler.rs +++ b/backend/src/api/handler.rs @@ -28,6 +28,7 @@ pub enum ApiMessage { LoadSystemSettings, GetLimits(Callback), GetProvider(String, Callback), + UploadCurrentVariant(String, String), // SteamID, Steam username } pub enum BatteryMessage { @@ -251,7 +252,7 @@ impl GeneralMessage { cb(Vec::with_capacity(0)) }, }, - Self::ApplyNow => {} + Self::ApplyNow => {}, } dirty } @@ -304,7 +305,7 @@ impl ApiMessageHandler { if is_persistent { let settings_clone = settings.json(); let save_json: SettingsJson = settings_clone.into(); - if let Err(e) = crate::persist::FileJson::update_variant_or_create(&save_path, save_json, settings.general.get_name().to_owned()) { + if let Err(e) = crate::persist::FileJson::update_variant_or_create(&save_path, settings.general.get_app_id(), save_json, settings.general.get_name().to_owned()) { log::error!("Failed to create/update settings file {}: {}", save_path.display(), e); } //unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); @@ -383,7 +384,7 @@ impl ApiMessageHandler { } ApiMessage::LoadSettings(id, name, variant_id, variant_name) => { let path = format!("{}.ron", id); - match settings.load_file(path.into(), name, variant_id, variant_name, false) { + match settings.load_file(path.into(), id, name, variant_id, variant_name, false) { Ok(success) => log::info!("Loaded settings file? {}", success), Err(e) => log::warn!("Load file err: {}", e), } @@ -391,7 +392,8 @@ impl ApiMessageHandler { } ApiMessage::LoadVariant(variant_id, variant_name) => { let path = settings.general.get_path(); - match settings.load_file(path.into(), settings.general.get_name().to_owned(), variant_id, variant_name, false) { + let app_id = settings.general.get_app_id(); + match settings.load_file(path.into(), app_id, settings.general.get_name().to_owned(), variant_id, variant_name, false) { Ok(success) => log::info!("Loaded settings file? {}", success), Err(e) => log::warn!("Load file err: {}", e), } @@ -400,6 +402,7 @@ impl ApiMessageHandler { ApiMessage::LoadMainSettings => { match settings.load_file( crate::consts::DEFAULT_SETTINGS_FILE.into(), + 0, crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), 0, crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), @@ -431,7 +434,13 @@ impl ApiMessageHandler { _ => settings.general.provider(), }); false - } + }, + ApiMessage::UploadCurrentVariant(steam_id, steam_username) => { + //TODO + let steam_app_id = settings.general.get_app_id(); + super::web::upload_settings(steam_app_id, steam_id, steam_username, settings.json()); + false + }, } } diff --git a/backend/src/api/web.rs b/backend/src/api/web.rs index a7b5f0d..08b9eab 100644 --- a/backend/src/api/web.rs +++ b/backend/src/api/web.rs @@ -87,34 +87,104 @@ fn web_config_to_settings_json(meta: community_settings_core::v1::Metadata) -> c } } +fn download_config(id: u128) -> std::io::Result { + let req_url = format!("{}/api/setting/by_id/{}", BASE_URL, id); + let response = ureq::get(&req_url).call() + .map_err(|e| { + log::warn!("GET to {} failed: {}", req_url, e); + std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e) + })?; + response.into_json() +} + +pub fn upload_settings(id: u64, user_id: String, username: String, settings: crate::persist::SettingsJson) { + log::info!("Uploading settings {} by {} ({})", settings.name, username, user_id); + let user_id: u64 = match user_id.parse() { + Ok(id) => id, + Err(e) => { + log::error!("Failed to parse `{}` as u64: {} (aborted upload_settings very early)", user_id, e); + return; + } + }; + let meta = settings_to_web_config(id as _, user_id, username, settings); + if let Err(e) = upload_config(meta) { + log::error!("Failed to upload settings: {}", e); + } +} + +fn settings_to_web_config(app_id: u32, user_id: u64, username: String, settings: crate::persist::SettingsJson) -> community_settings_core::v1::Metadata { + community_settings_core::v1::Metadata { + name: settings.name, + steam_app_id: app_id, + steam_user_id: user_id, + steam_username: username, + tags: vec!["wip".to_owned()], + id: "".to_owned(), + config: community_settings_core::v1::Config { + cpus: settings.cpus.into_iter().map(|cpu| community_settings_core::v1::Cpu { + online: cpu.online, + clock_limits: cpu.clock_limits.map(|lim| community_settings_core::v1::MinMax { + min: lim.min, + max: lim.max, + }), + governor: cpu.governor, + }).collect(), + gpu: community_settings_core::v1::Gpu { + fast_ppt: settings.gpu.fast_ppt, + slow_ppt: settings.gpu.slow_ppt, + tdp: settings.gpu.tdp, + tdp_boost: settings.gpu.tdp_boost, + clock_limits: settings.gpu.clock_limits.map(|lim| community_settings_core::v1::MinMax { + min: lim.min, + max: lim.max, + }), + slow_memory: settings.gpu.slow_memory, + }, + battery: community_settings_core::v1::Battery { + charge_rate: settings.battery.charge_rate, + charge_mode: settings.battery.charge_mode, + events: settings.battery.events.into_iter().map(|batt_ev| community_settings_core::v1::BatteryEvent { + trigger: batt_ev.trigger, + charge_rate: batt_ev.charge_rate, + charge_mode: batt_ev.charge_mode, + }).collect(), + }, + }, + } +} + +fn upload_config(config: community_settings_core::v1::Metadata) -> std::io::Result<()> { + let req_url = format!("{}/api/setting", BASE_URL); + ureq::post(&req_url) + .send_json(&config) + .map_err(|e| { + log::warn!("POST to {} failed: {}", req_url, e); + std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e) + }) + .map(|_| ()) +} + /// Download config web method pub fn download_new_config(sender: Sender) -> impl AsyncCallable { let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety let getter = move || { let sender2 = sender.clone(); move |id: u128| { - let req_url = format!("{}/api/setting/by_id/{}", BASE_URL, id); - match ureq::get(&req_url).call() { - Ok(response) => { - let json_res: std::io::Result = response.into_json(); - match json_res { - Ok(meta) => { - let (tx, rx) = mpsc::channel(); - let callback = - move |values: Vec| tx.send(values).expect("download_new_config callback send failed"); - sender2 - .lock() - .unwrap() - .send(ApiMessage::General(GeneralMessage::AddVariant(web_config_to_settings_json(meta), Box::new(callback)))) - .expect("download_new_config send failed"); - return rx.recv().expect("download_new_config callback recv failed"); - } - Err(e) => { - log::error!("Cannot parse response from `{}`: {}", req_url, e) - } - } + match download_config(id) { + Ok(meta) => { + let (tx, rx) = mpsc::channel(); + let callback = + move |values: Vec| tx.send(values).expect("download_new_config callback send failed"); + sender2 + .lock() + .unwrap() + .send(ApiMessage::General(GeneralMessage::AddVariant(web_config_to_settings_json(meta), Box::new(callback)))) + .expect("download_new_config send failed"); + return rx.recv().expect("download_new_config callback recv failed"); + }, + Err(e) => { + log::error!("Invalid response from download: {}", e); } - Err(e) => log::warn!("Cannot get setting result from `{}`: {}", req_url, e), } vec![] } @@ -140,3 +210,36 @@ pub fn download_new_config(sender: Sender) -> impl AsyncCallable { }, } } + +/// Upload currently-loaded variant +pub fn upload_current_variant(sender: Sender) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move |(steam_id, steam_username): (String, String)| { + sender2 + .lock() + .unwrap() + .send(ApiMessage::UploadCurrentVariant(steam_id, steam_username)) + .expect("upload_current_variant send failed"); + true + } + }; + super::async_utils::AsyncIsh { + trans_setter: |params| { + if let Some(Primitive::String(steam_id)) = params.get(0) { + if let Some(Primitive::String(steam_username)) = params.get(1) { + Ok((steam_id.to_owned(), steam_username.to_owned())) + } else { + Err("upload_current_variant missing/invalid parameter 1".to_owned()) + } + } else { + Err("upload_current_variant missing/invalid parameter 0".to_owned()) + } + }, + set_get: getter, + trans_getter: |result| { + vec![result.into()] + }, + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 121630a..e39bec6 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -77,15 +77,17 @@ fn main() -> Result<(), ()> { let mut loaded_settings = persist::FileJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) .map(|mut file| file.variants.remove(&0) - .map(|settings| settings::Settings::from_json(DEFAULT_SETTINGS_NAME.into(), settings, DEFAULT_SETTINGS_FILE.into())) + .map(|settings| settings::Settings::from_json(DEFAULT_SETTINGS_NAME.into(), settings, DEFAULT_SETTINGS_FILE.into(), 0)) .unwrap_or_else(|| settings::Settings::system_default( DEFAULT_SETTINGS_FILE.into(), + 0, DEFAULT_SETTINGS_NAME.into(), 0, DEFAULT_SETTINGS_VARIANT_NAME.into()))) .unwrap_or_else(|_| { settings::Settings::system_default( DEFAULT_SETTINGS_FILE.into(), + 0, DEFAULT_SETTINGS_NAME.into(), 0, DEFAULT_SETTINGS_VARIANT_NAME.into(), @@ -320,6 +322,10 @@ fn main() -> Result<(), ()> { .register_async( "WEB_download_new", api::web::download_new_config(api_sender.clone()) + ) + .register_async( + "WEB_upload_new", + api::web::upload_current_variant(api_sender.clone()) ); utility::ioperm_power_ec(); diff --git a/backend/src/persist/file.rs b/backend/src/persist/file.rs index b6cdee2..c4a4010 100644 --- a/backend/src/persist/file.rs +++ b/backend/src/persist/file.rs @@ -9,6 +9,7 @@ use super::SettingsJson; pub struct FileJson { pub version: u64, pub name: String, + pub app_id: u64, pub variants: HashMap, } @@ -44,7 +45,7 @@ impl FileJson { .unwrap_or(0) } - pub fn update_variant_or_create>(path: P, mut setting: SettingsJson, given_name: String) -> Result { + pub fn update_variant_or_create>(path: P, app_id: u64, mut setting: SettingsJson, given_name: String) -> Result { if !setting.persistent { return Self::open(path) } @@ -62,6 +63,7 @@ impl FileJson { setting_variants.insert(setting.variant, setting); Self { version: 0, + app_id: app_id, name: given_name, variants: setting_variants, } diff --git a/backend/src/settings/detect/auto_detect.rs b/backend/src/settings/detect/auto_detect.rs index e7b8ded..9556d42 100644 --- a/backend/src/settings/detect/auto_detect.rs +++ b/backend/src/settings/detect/auto_detect.rs @@ -62,6 +62,7 @@ pub fn auto_detect_provider() -> DriverJson { let provider = auto_detect0( None, crate::utility::settings_dir().join("autodetect.json"), + 0, "".to_owned(), 0, crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), @@ -76,6 +77,7 @@ pub fn auto_detect_provider() -> DriverJson { pub fn auto_detect0( settings_opt: Option<&SettingsJson>, json_path: std::path::PathBuf, + app_id: u64, name: String, variant_id: u64, variant_name: String, @@ -83,6 +85,7 @@ pub fn auto_detect0( let mut general_driver = Box::new(General { persistent: false, path: json_path, + app_id, name, variant_id, variant_name, diff --git a/backend/src/settings/driver.rs b/backend/src/settings/driver.rs index 1aa005e..6f94fff 100644 --- a/backend/src/settings/driver.rs +++ b/backend/src/settings/driver.rs @@ -13,14 +13,15 @@ impl Driver { name: String, settings: &SettingsJson, json_path: std::path::PathBuf, + app_id: u64, ) -> Self { let name_bup = settings.name.clone(); let id_bup = settings.variant; - auto_detect0(Some(settings), json_path, name, id_bup, name_bup) + auto_detect0(Some(settings), json_path, app_id, name, id_bup, name_bup) } - pub fn system_default(json_path: std::path::PathBuf, name: String, variant_id: u64, variant_name: String) -> Self { - auto_detect0(None, json_path, name, variant_id, variant_name) + pub fn system_default(json_path: std::path::PathBuf, app_id: u64, name: String, variant_id: u64, variant_name: String) -> Self { + auto_detect0(None, json_path, app_id, name, variant_id, variant_name) } } diff --git a/backend/src/settings/general.rs b/backend/src/settings/general.rs index 75167a8..d1d913a 100644 --- a/backend/src/settings/general.rs +++ b/backend/src/settings/general.rs @@ -32,6 +32,7 @@ impl std::fmt::Display for SettingVariant { pub struct General { pub persistent: bool, pub path: PathBuf, + pub app_id: u64, pub name: String, pub variant_id: u64, pub variant_name: String, @@ -73,6 +74,14 @@ impl TGeneral for General { self.path = path; } + fn app_id(&mut self) -> &'_ mut u64 { + &mut self.app_id + } + + fn get_app_id(&self) -> u64 { + self.app_id + } + fn get_name(&self) -> &'_ str { &self.name } @@ -108,7 +117,7 @@ impl TGeneral for General { fn add_variant(&self, variant: crate::persist::SettingsJson) -> Result, SettingError> { let variant_name = variant.name.clone(); - crate::persist::FileJson::update_variant_or_create(self.get_path(), variant, variant_name) + crate::persist::FileJson::update_variant_or_create(self.get_path(), self.get_app_id(), variant, variant_name) .map_err(|e| SettingError { msg: format!("failed to add variant: {}", e), setting: SettingVariant::General, @@ -173,8 +182,8 @@ impl OnSet for Settings { impl Settings { #[inline] - pub fn from_json(name: String, other: SettingsJson, json_path: PathBuf) -> Self { - let x = super::Driver::init(name, &other, json_path.clone()); + pub fn from_json(name: String, other: SettingsJson, json_path: PathBuf, app_id: u64) -> Self { + let x = super::Driver::init(name, &other, json_path.clone(), app_id); log::info!( "Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), @@ -190,8 +199,8 @@ impl Settings { } } - pub fn system_default(json_path: PathBuf, name: String, variant_id: u64, variant_name: String) -> Self { - let driver = super::Driver::system_default(json_path, name, variant_id, variant_name); + pub fn system_default(json_path: PathBuf, app_id: u64, name: String, variant_id: u64, variant_name: String) -> Self { + let driver = super::Driver::system_default(json_path, app_id, name, variant_id, variant_name); Self { general: driver.general, cpus: driver.cpus, @@ -201,7 +210,7 @@ impl Settings { } pub fn load_system_default(&mut self, name: String, variant_id: u64, variant_name: String) { - let driver = super::Driver::system_default(self.general.get_path().to_owned(), name, variant_id, variant_name); + let driver = super::Driver::system_default(self.general.get_path().to_owned(), self.general.get_app_id(), name, variant_id, variant_name); self.cpus = driver.cpus; self.gpu = driver.gpu; self.battery = driver.battery; @@ -222,6 +231,7 @@ impl Settings { pub fn load_file( &mut self, filename: PathBuf, + app_id: u64, name: String, variant: u64, variant_name: String, @@ -231,7 +241,7 @@ impl Settings { if json_path.exists() { if variant == u64::MAX { *self.general.persistent() = true; - let file_json = FileJson::update_variant_or_create(&json_path, self.json(), variant_name.clone()).map_err(|e| SettingError { + let file_json = FileJson::update_variant_or_create(&json_path, app_id, self.json(), variant_name.clone()).map_err(|e| SettingError { msg: format!("Failed to open settings {}: {}", json_path.display(), e), setting: SettingVariant::General, })?; @@ -252,7 +262,7 @@ impl Settings { *self.general.persistent() = false; self.general.name(name); } else { - let x = super::Driver::init(name, settings_json, json_path.clone()); + let x = super::Driver::init(name, settings_json, json_path.clone(), app_id); log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider()); self.general = x.general; self.cpus = x.cpus; @@ -270,6 +280,7 @@ impl Settings { } *self.general.persistent() = false; } + *self.general.app_id() = app_id; self.general.path(filename); self.general.variant_id(variant); Ok(*self.general.persistent()) diff --git a/backend/src/settings/traits.rs b/backend/src/settings/traits.rs index cacff0a..dd0fc69 100644 --- a/backend/src/settings/traits.rs +++ b/backend/src/settings/traits.rs @@ -105,6 +105,10 @@ pub trait TGeneral: OnSet + OnResume + OnPowerEvent + Debug + Send { fn path(&mut self, path: std::path::PathBuf); + fn app_id(&mut self) -> &'_ mut u64; + + fn get_app_id(&self) -> u64; + fn get_name(&self) -> &'_ str; fn name(&mut self, name: String); diff --git a/src/backend.ts b/src/backend.ts index 3f69d87..a04a79a 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -383,6 +383,10 @@ export async function storeDownloadById(id: string): Promise { return (await call_backend("WEB_download_new", [id])); } +export async function storeUpload(steam_id: string, steam_username: string): Promise { + return (await call_backend("WEB_upload_new", [steam_id, steam_username])); +} + export async function getAllSettingVariants(): Promise { console.log("GENERAL_get_all_variants"); return (await call_backend("GENERAL_get_all_variants", [])); diff --git a/src/components/battery.tsx b/src/components/battery.tsx index 0199e1b..59b476d 100644 --- a/src/components/battery.tsx +++ b/src/components/battery.tsx @@ -1,5 +1,5 @@ import { Fragment } from "react"; -import {Component} from "react"; +import { Component } from "react"; import { ToggleField, SliderField, diff --git a/src/consts.ts b/src/consts.ts index 415f5a5..8fc068a 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -35,6 +35,13 @@ export const CURRENT_VARIANT_GEN = "GENERAL_current_variant"; export const MESSAGE_LIST = "MESSAGE_messages"; +export const INTERNAL_STEAM_ID = "INTERNAL_steam_id"; +export const INTERNAL_STEAM_USERNAME = "INTERNAL_stream_username"; + +export const STORE_RESULTS = "INTERNAL_store_results"; + export const PERIODICAL_BACKEND_PERIOD = 5000; // milliseconds export const AUTOMATIC_REAPPLY_WAIT = 2000; // milliseconds +export const STORE_RESULTS_URI = "/plugins/PowerTools/settings_store"; + diff --git a/src/index.tsx b/src/index.tsx index 3fff822..3348742 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,6 +15,7 @@ import { Field, Dropdown, SingleDropdownOption, + Navigation, //NotchLabel //gamepadDialogClasses, //joinClassNames, @@ -22,6 +23,7 @@ import { import { VFC, useState } from "react"; import { GiDrill, GiTimeBomb, GiTimeTrap, GiDynamite } from "react-icons/gi"; import { HiRefresh, HiTrash, HiPlus, HiUpload } from "react-icons/hi"; +import { TbWorldPlus } from "react-icons/tb"; //import * as python from "./python"; import * as backend from "./backend"; @@ -63,6 +65,12 @@ import { MESSAGE_LIST, + INTERNAL_STEAM_ID, + INTERNAL_STEAM_USERNAME, + + STORE_RESULTS, + STORE_RESULTS_URI, + PERIODICAL_BACKEND_PERIOD, AUTOMATIC_REAPPLY_WAIT, } from "./consts"; @@ -73,10 +81,13 @@ import { Battery } from "./components/battery"; import { Cpus } from "./components/cpus"; import { DevMessages } from "./components/message"; +import { StoreResultsPage } from "./store/page"; + var periodicHook: NodeJS.Timeout | null = null; var lifetimeHook: any = null; var startHook: any = null; var endHook: any = null; +var userHook: any = null; var usdplReady = false; var tryNotifyProfileChange = function() {}; @@ -118,6 +129,10 @@ const reload = function() { console.debug("POWERTOOLS: got limits ", limits); }); + if (!get_value(STORE_RESULTS)) { + backend.resolve(backend.searchStoreByAppId(0), (results) => set_value(STORE_RESULTS, results)); + } + backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeRate(), (rate: number | null) => { set_value(CHARGE_RATE_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeMode(), (mode: string | null) => { set_value(CHARGE_MODE_BATT, mode) }); @@ -175,6 +190,7 @@ const clearHooks = function() { lifetimeHook?.unregister(); startHook?.unregister(); endHook?.unregister(); + userHook?.unregister(); backend.log(backend.LogLevel.Info, "Unregistered PowerTools callbacks, so long and thanks for all the fish."); }; @@ -209,7 +225,6 @@ const registerCallbacks = function(autoclear: boolean) { let appId = gameInfo.appid.toString(); backend.log(backend.LogLevel.Info, "RegisterForGameActionStart callback(" + actionType + ", " + id + ")"); - // don't use gameInfo.appid, haha backend.resolve( backend.loadGeneralSettings(appId, gameInfo.display_name, "0", undefined), (ok: boolean) => { @@ -221,6 +236,12 @@ const registerCallbacks = function(autoclear: boolean) { }); } ); + backend.resolve( + backend.searchStoreByAppId(appId), + (results: backend.StoreMetadata[]) => { + set_value(STORE_RESULTS, results); + } + ); }); // this fires immediately, so let's ignore that callback @@ -236,6 +257,20 @@ const registerCallbacks = function(autoclear: boolean) { setTimeout(() => backend.forceApplySettings(), AUTOMATIC_REAPPLY_WAIT); }); + //@ts-ignore + userHook = SteamClient.User.RegisterForCurrentUserChanges((data) => { + const accountName = data.strAccountName; + const steamId = data.strSteamID; + SteamClient.User.GetLoginUsers().then((users: any) => { + users.forEach((user: any) => { + if (user && user.accountName == accountName) { + set_value(INTERNAL_STEAM_ID, steamId); + set_value(INTERNAL_STEAM_USERNAME, user.personaName ? user.personaName : accountName); + } + }); + }); + }); + backend.log(backend.LogLevel.Debug, "Registered PowerTools callbacks, hello!"); }; @@ -373,7 +408,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }}> = ({}) => { { - backend.log(backend.LogLevel.Debug, "Clicked on unimplemented upload button"); + const steamId = get_value(INTERNAL_STEAM_ID); + const steamName = get_value(INTERNAL_STEAM_USERNAME); + if (steamId && steamName) { + backend.storeUpload(steamId, steamName); + } else { + backend.log(backend.LogLevel.Warn, "Cannot upload with null steamID (is null: " + !steamId + ") and/or username (is null: " + !steamName + ")"); + } }} > + { + Navigation.Navigate(STORE_RESULTS_URI); + Navigation.CloseSideMenus(); + }} + > + + @@ -453,6 +507,7 @@ export default definePlugin((serverApi: ServerAPI) => { ico = ; } //registerCallbacks(false); + serverApi.routerHook.addRoute(STORE_RESULTS_URI, StoreResultsPage); return { title:
PowerTools
, content: , diff --git a/src/store/page.tsx b/src/store/page.tsx new file mode 100644 index 0000000..74926e2 --- /dev/null +++ b/src/store/page.tsx @@ -0,0 +1,48 @@ +import { Component, Fragment } from "react"; + +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { get_value} from "usdpl-front"; + +import { + STORE_RESULTS, +} from "../consts"; + +export class StoreResultsPage extends Component { + constructor() { + super({}); + this.state = { + reloadThingy: "/shrug", + }; + } + + render() { + const storeItems = get_value(STORE_RESULTS) as backend.StoreMetadata[] | undefined; + console.log("POWERTOOLS: Rendering store results", storeItems); + if (storeItems) { + if (storeItems.length == 0) { + backend.log(backend.LogLevel.Warn, "No store results; got array with length 0 from cache"); + return (
+ { tr("No results") /* TODO translate */ } +
); + } else { + // TODO + return storeItems.map((meta: backend.StoreMetadata) => { +
+
{ meta.name }
+
{ tr("Created by") /* TODO translate */} { meta.steam_username }
+
{ meta.tags.map((tag: string) => {tag}) }
+ Hey NG you should finish this page +
+ }); + } + + } else { + backend.log(backend.LogLevel.Warn, "Store failed to load; got null from cache"); + // store did not pre-load when the game started + return ( + { tr("Store failed to load") /* TODO translate */ } + ); + } + } +}