Skip to content

Commit

Permalink
Updated UI & backend further
Browse files Browse the repository at this point in the history
  • Loading branch information
CEbbinghaus committed Oct 24, 2023
1 parent 663767b commit ddca40d
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 119 deletions.
2 changes: 1 addition & 1 deletion backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "backend"
version = "0.9.2"
version = "0.9.3"
edition = "2021"
license = "GPL-2.0"
authors = ["Christopher-Robin Ebbinghaus <git@cebbinghaus.com>"]
Expand Down
93 changes: 64 additions & 29 deletions backend/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use serde::Deserialize;
use std::{ops::Deref, sync::Arc};

pub(crate) fn config(cfg: &mut web::ServiceConfig) {
cfg.service(create_card)
cfg.service(get_current_card)
.service(get_current_card_id)
.service(get_current_card_and_games)
.service(create_card)
.service(delete_card)
.service(list_cards)
.service(get_card)
Expand All @@ -23,9 +26,15 @@ pub(crate) fn config(cfg: &mut web::ServiceConfig) {
.service(get_games_on_current_card)
.service(set_name_for_card)
.service(create_link)
.service(health)
.service(save);
}

#[get("/health")]
pub(crate) async fn health() -> impl Responder {
HttpResponse::Ok()
}

#[get("/ListCardsWithGames")]
pub(crate) async fn list_cards_with_games(datastore: web::Data<Arc<Store>>) -> impl Responder {
web::Json(datastore.list_cards_with_games())
Expand Down Expand Up @@ -53,31 +62,6 @@ pub(crate) async fn get_cards_for_game(
}
}

#[get("/GetGamesOnCurrentCard")]
pub(crate) async fn get_games_on_current_card(
datastore: web::Data<Arc<Store>>,
) -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
}

let uid = get_card_cid().ok_or(Error::from_str("Unable to evaluate Card Id"))?;

match datastore.get_games_on_card(&uid) {
Ok(value) => Ok(web::Json(value)),
Err(err) => Err(actix_web::Error::from(err)),
}
}

#[get("/GetCurrentCardId")]
pub(crate) async fn get_current_card_id() -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
}

Ok(get_card_cid().ok_or(Error::from_str("Unable to evaluate Card Id"))?)
}

#[derive(Deserialize)]
pub struct SetNameForCardBody {
id: String,
Expand All @@ -96,7 +80,18 @@ pub(crate) async fn set_name_for_card(
Ok(HttpResponse::Ok())
}

#[get("/card/current")]
#[get("/current")]
pub(crate) async fn get_current_card_and_games(datastore: web::Data<Arc<Store>>) -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
}

let uid = get_card_cid().ok_or(Error::Error("Unable to evaluate Card Id".into()))?;

Ok(web::Json(datastore.get_card_and_games(&uid)?))
}

#[get("/current/card")]
pub(crate) async fn get_current_card(datastore: web::Data<Arc<Store>>) -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
Expand All @@ -107,14 +102,44 @@ pub(crate) async fn get_current_card(datastore: web::Data<Arc<Store>>) -> Result
Ok(web::Json(datastore.get_card(&uid)?))
}

#[get("/current/id")]
pub(crate) async fn get_current_card_id() -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
}

Ok(get_card_cid().ok_or(Error::from_str("Unable to evaluate Card Id"))?)
}

#[get("/current/games")]
pub(crate) async fn get_games_on_current_card(
datastore: web::Data<Arc<Store>>,
) -> Result<impl Responder> {
if !is_card_inserted() {
return Err(Error::from_str("No card is inserted").into());
}

let uid = get_card_cid().ok_or(Error::from_str("Unable to evaluate Card Id"))?;

match datastore.get_games_on_card(&uid) {
Ok(value) => Ok(web::Json(value)),
Err(err) => Err(actix_web::Error::from(err)),
}
}


#[post("/card/{id}")]
pub(crate) async fn create_card(
id: web::Path<String>,
body: web::Json<MicroSDCard>,
datastore: web::Data<Arc<Store>>,
) -> Result<impl Responder> {
if *id != body.uid {
return Err(Error::from_str("uid did not match id provided").into());
}

match datastore.contains_element(&id) {
// Merge the records allowing us to update all properties
// Merge the records allowing us to update all properties
true => datastore.update_card(&id, move |existing_card| {
existing_card.merge(body.deref().to_owned())?;
Ok(())
Expand Down Expand Up @@ -154,7 +179,17 @@ pub(crate) async fn create_game(
body: web::Json<Game>,
datastore: web::Data<Arc<Store>>,
) -> Result<impl Responder> {
datastore.add_game(id.into_inner(), body.into_inner());
if *id != body.uid {
return Err(Error::from_str("uid did not match id provided").into());
}

let mut game = body.to_owned();

if !cfg!(debug_assertions) {
game.is_steam = false;
}

datastore.add_game(id.into_inner(), game);

Ok(HttpResponse::Ok())
}
Expand Down
31 changes: 27 additions & 4 deletions backend/src/ds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ impl StoreData {
.or_insert_with(|| self.nodes.insert(Node::from_card(card)));
}

pub fn add_game(&mut self, id: String, card: Game) {
pub fn add_game(&mut self, id: String, game: Game) {
self.node_ids
.entry(id)
.or_insert_with(|| self.nodes.insert(Node::from_game(card)));
.or_insert_with(|| self.nodes.insert(Node::from_game(game)));
}

pub fn update_card<F>(&mut self, card_id: &str, mut func: F) -> Result<(), Error>
Expand Down Expand Up @@ -158,6 +158,25 @@ impl StoreData {
})
}


pub fn get_card_and_games(&self, card_id: &str) -> Result<(MicroSDCard, Vec<Game>), Error> {
let card_key = self
.node_ids
.get(card_id)
.ok_or_else(|| Error::from_str("Card Id not present"))?;

let node = &self.nodes[*card_key];

let card = node.element.as_card().ok_or(Error::from_str("Element was not a card"))?;
let games = node
.links
.iter()
.filter_map(|game_key| self.nodes[*game_key].element.as_game())
.collect();

Ok((card, games))
}

pub fn get_games_on_card(&self, card_id: &str) -> Result<Vec<Game>, Error> {
let card_key = self
.node_ids
Expand Down Expand Up @@ -315,8 +334,8 @@ impl Store {
self.try_write_to_file()
}

pub fn add_game(&self, id: String, card: Game) {
self.data.write().unwrap().add_game(id, card);
pub fn add_game(&self, id: String, game: Game) {
self.data.write().unwrap().add_game(id, game);
self.try_write_to_file()
}

Expand Down Expand Up @@ -362,6 +381,10 @@ impl Store {
self.data.read().unwrap().get_game(game_id)
}

pub fn get_card_and_games(&self, card_id: &str) -> Result<(MicroSDCard, Vec<Game>), Error> {
self.data.read().unwrap().get_card_and_games(card_id)
}

pub fn get_games_on_card(&self, card_id: &str) -> Result<Vec<Game>, Error> {
self.data.read().unwrap().get_games_on_card(card_id)
}
Expand Down
9 changes: 8 additions & 1 deletion backend/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ impl From<String> for Name {
}
}

fn default_true() -> bool {
true
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MicroSDCard {
pub uid: String,
Expand All @@ -31,7 +35,7 @@ pub struct MicroSDCard {
#[serde(default)]
pub position: u32,
#[serde(default)]
pub hidden: bool
pub hidden: bool,
}

impl MicroSDCard {
Expand All @@ -57,4 +61,7 @@ pub struct Game {
pub uid: String,
pub name: String,
pub size: u64,

#[serde(default="default_true")]
pub is_steam: bool
}
2 changes: 2 additions & 0 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ async fn main() {
.expect("should retrieve data directory"),
),
);

println!("Loading from store \"{:?}\"", store_path);
let store: Arc<Store> =
Arc::new(Store::read_from_file(store_path.clone()).unwrap_or(Store::new(Some(store_path))));

Expand Down
5 changes: 3 additions & 2 deletions backend/src/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ fn read_msd_directory(datastore: &Store) -> Result<(), Error> {
uid: cid.clone(),
libid: library.contentid.clone(),
name: library.label,
position: 0,
hidden: false
position: 0,
hidden: false,
},
);
}
Expand All @@ -109,6 +109,7 @@ fn read_msd_directory(datastore: &Store) -> Result<(), Error> {
uid: game.appid.clone(),
name: game.name.clone(),
size: game.size_on_disk,
is_steam: true,
},
);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "microsdeck",
"version": "0.9.2",
"version": "0.9.3",
"description": "A SteamDeck plugin to track games across MicroSD cards",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
Expand Down
48 changes: 48 additions & 0 deletions src/components/CardActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { MenuItem, showModal, Menu, ConfirmModal } from "decky-frontend-lib"
import { CardAndGames, MicroSDCard } from "../lib/Types";
import { MicroSDeckManager } from "../state/MicoSDeckManager";
import { EditCardModal } from "../modals/EditCardModal";
import { UNAMED_CARD_NAME } from "../const";

interface CardActionsContextMenuProps {
microSDeckManager: MicroSDeckManager,
currentCard: MicroSDCard | undefined,
cardAndGames: CardAndGames
}

/**
* The context menu for Tab Actions.
*/
export function CardActionsContextMenu({ cardAndGames, currentCard, microSDeckManager }: CardActionsContextMenuProps) {
const [card, games] = cardAndGames;

return (
<Menu label="Actions">
<MenuItem onSelected={() => {
showModal(<EditCardModal
onConfirm={(card: MicroSDCard) => {
microSDeckManager.updateCard(card);
}}
card={{ ...card }}
games={games}
/>);
}}>
Edit
</MenuItem>
<MenuItem onSelected={() => microSDeckManager.hideCard(card)}>
Hide
</MenuItem>
<MenuItem tone="destructive" disabled={card.uid == currentCard?.uid} onSelected={() => {
showModal(<ConfirmModal
bAllowFullSize
strTitle={`Are you sure you want to delete ${card.name || UNAMED_CARD_NAME}`}
onOK={() => microSDeckManager.deleteCard(card)}
strOKButtonText="Confirm">
This cannot be undone. If you insert the card it will be registered again but any changes you have made will be lost.
</ConfirmModal>);
}}>
Delete
</MenuItem>
</Menu>
)
}
5 changes: 3 additions & 2 deletions src/components/LibraryModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { ReactElement, useEffect, useRef, useState} from 'react'
import { FaSdCard } from 'react-icons/fa'
import { GetCardsForGame } from '../hooks/backend';
import { Logger } from '../Logging';
import { UNAMED_CARD_NAME } from '../const';

export default function LibraryModal({appId}: {appId: string}): ReactElement {
var ref = useRef();
Expand Down Expand Up @@ -57,8 +58,8 @@ export default function LibraryModal({appId}: {appId: string}): ReactElement {
>
<FaSdCard size={20} />
<span>
{value.map(v => v.name).filter(v => v).join(", ") || "UNAMED"}
{value.map(v => v.name || UNAMED_CARD_NAME).join(", ")}
</span>
</div>
)
}
}
6 changes: 4 additions & 2 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ export const PROTOCOL: string = "http";

export const API_URL: string = `${PROTOCOL}://${HOST}${PORT ? (":" + PORT) : ""}`;

export const CONFIGURATION_PATH = "/microsdeck/config"
export const DOCUMENTATION_PATH = "/microsdeck/docs"
export const CONFIGURATION_PATH = "/microsdeck/config";
export const DOCUMENTATION_PATH = "/microsdeck/docs";

export const UNAMED_CARD_NAME = "Unamed Card";
Loading

0 comments on commit ddca40d

Please sign in to comment.