diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5a74eb0..66abeb9 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -311,7 +311,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backend" -version = "0.9.2" +version = "0.9.3" dependencies = [ "actix-cors", "actix-web", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c98bf8e..7ea1174 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -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 "] diff --git a/backend/src/api.rs b/backend/src/api.rs index 96d22c1..0dc52ad 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -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) @@ -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>) -> impl Responder { web::Json(datastore.list_cards_with_games()) @@ -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>, -) -> Result { - 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 { - 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, @@ -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>) -> Result { + 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>) -> Result { if !is_card_inserted() { return Err(Error::from_str("No card is inserted").into()); @@ -107,14 +102,44 @@ pub(crate) async fn get_current_card(datastore: web::Data>) -> Result Ok(web::Json(datastore.get_card(&uid)?)) } +#[get("/current/id")] +pub(crate) async fn get_current_card_id() -> Result { + 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>, +) -> Result { + 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, body: web::Json, datastore: web::Data>, ) -> Result { + 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(()) @@ -154,7 +179,17 @@ pub(crate) async fn create_game( body: web::Json, datastore: web::Data>, ) -> Result { - 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()) } diff --git a/backend/src/ds.rs b/backend/src/ds.rs index 3321988..8a0df3a 100644 --- a/backend/src/ds.rs +++ b/backend/src/ds.rs @@ -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(&mut self, card_id: &str, mut func: F) -> Result<(), Error> @@ -158,6 +158,25 @@ impl StoreData { }) } + + pub fn get_card_and_games(&self, card_id: &str) -> Result<(MicroSDCard, Vec), 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, Error> { let card_key = self .node_ids @@ -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() } @@ -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), Error> { + self.data.read().unwrap().get_card_and_games(card_id) + } + pub fn get_games_on_card(&self, card_id: &str) -> Result, Error> { self.data.read().unwrap().get_games_on_card(card_id) } diff --git a/backend/src/dto.rs b/backend/src/dto.rs index 9ebf59d..9cfcc09 100644 --- a/backend/src/dto.rs +++ b/backend/src/dto.rs @@ -22,6 +22,10 @@ impl From for Name { } } +fn default_true() -> bool { + true +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct MicroSDCard { pub uid: String, @@ -31,7 +35,7 @@ pub struct MicroSDCard { #[serde(default)] pub position: u32, #[serde(default)] - pub hidden: bool + pub hidden: bool, } impl MicroSDCard { @@ -57,4 +61,7 @@ pub struct Game { pub uid: String, pub name: String, pub size: u64, + + #[serde(default="default_true")] + pub is_steam: bool } diff --git a/backend/src/main.rs b/backend/src/main.rs index 76cc2c5..a923f52 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -90,6 +90,8 @@ async fn main() { .expect("should retrieve data directory"), ), ); + + println!("Loading from store \"{:?}\"", store_path); let store: Arc = Arc::new(Store::read_from_file(store_path.clone()).unwrap_or(Store::new(Some(store_path)))); diff --git a/backend/src/watch.rs b/backend/src/watch.rs index 9981727..041df99 100644 --- a/backend/src/watch.rs +++ b/backend/src/watch.rs @@ -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, }, ); } @@ -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, }, ); } diff --git a/package.json b/package.json index dc3249b..a65ddc4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/CardActions.tsx b/src/components/CardActions.tsx new file mode 100644 index 0000000..13c5c7e --- /dev/null +++ b/src/components/CardActions.tsx @@ -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 ( + + { + showModal( { + microSDeckManager.updateCard(card); + }} + card={{ ...card }} + games={games} + />); + }}> + Edit + + microSDeckManager.hideCard(card)}> + Hide + + { + showModal( 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. + ); + }}> + Delete + + + ) +} diff --git a/src/components/LibraryModal.tsx b/src/components/LibraryModal.tsx index 415573c..62e0521 100644 --- a/src/components/LibraryModal.tsx +++ b/src/components/LibraryModal.tsx @@ -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(); @@ -57,8 +58,8 @@ export default function LibraryModal({appId}: {appId: string}): ReactElement { > - {value.map(v => v.name).filter(v => v).join(", ") || "UNAMED"} + {value.map(v => v.name || UNAMED_CARD_NAME).join(", ")} ) -} \ No newline at end of file +} diff --git a/src/const.ts b/src/const.ts index 65afb31..1a7d911 100644 --- a/src/const.ts +++ b/src/const.ts @@ -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" \ No newline at end of file +export const CONFIGURATION_PATH = "/microsdeck/config"; +export const DOCUMENTATION_PATH = "/microsdeck/docs"; + +export const UNAMED_CARD_NAME = "Unamed Card"; diff --git a/src/hooks/backend.ts b/src/hooks/backend.ts index 9bc32f1..f9685bd 100644 --- a/src/hooks/backend.ts +++ b/src/hooks/backend.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { Logger } from '../Logging'; import { API_URL } from '../const'; -import { CardsAndGames, MicroSDCard } from '../lib/Types'; +import { CardAndGames, CardsAndGames, MicroSDCard } from '../lib/Types'; export async function SetNameForMicroSDCard(CardId: string, Name: string) { await fetch(`${API_URL}/SetNameForCard`, { @@ -12,7 +12,7 @@ export async function SetNameForMicroSDCard(CardId: string, Name: string) { body: JSON.stringify({ id: CardId, name: Name }), referrerPolicy: "unsafe-url", }) - .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); + .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); } export function GetCardsForGame(appId: string) { @@ -38,14 +38,43 @@ export function GetCardsForGame(appId: string) { } } +export async function fetchDeleteCard(card: MicroSDCard) { + await fetch(`${API_URL}/card/${card.uid}`, { + method: "DELETE", + referrerPolicy: "unsafe-url", + }) + .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); +} + +export async function fetchUpdateCard(card: MicroSDCard) { + await fetch(`${API_URL}/card/${card.uid}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(card), + referrerPolicy: "unsafe-url", + }) + .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); +} + +export async function fetchCurrentCardAndGames(): Promise { + return await fetch(`${API_URL}/current`, { referrerPolicy: "unsafe-url", }) + .then(res => res.json()) + .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); +} +export async function fetchCardsAndGames(): Promise { + return await fetch(`${API_URL}/ListCardsWithGames`, { referrerPolicy: "unsafe-url", }) + .then(res => res.json()) + .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); +} + export function GetCardsAndGames() { const [cards, setValue] = useState(null) async function runQuery() { - const result = await fetch(`${API_URL}/ListCardsWithGames`, { referrerPolicy: "unsafe-url", }) - .then(res => res.json()) - .catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error })); - setValue(result) + const result = await fetchCardsAndGames(); + setValue(result || null) } useEffect(() => { diff --git a/src/index.tsx b/src/index.tsx index dc27772..6a8b5f3 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,25 +6,23 @@ import { PanelSection, ReorderableEntry, ReorderableList, - ScrollPanel, ServerAPI, - showModal, + showContextMenu, staticClasses, - TextField, } from "decky-frontend-lib"; -import { useState, VFC } from "react"; -import { FaPen, FaSdCard } from "react-icons/fa"; +import { FaEllipsisH, FaSdCard } from "react-icons/fa"; import PatchAppScreen from "./patch/PatchAppScreen"; -import { DOCUMENTATION_PATH } from "./const"; +import { DOCUMENTATION_PATH, UNAMED_CARD_NAME } from "./const"; import { Logger } from "./Logging"; -import { GetCardsAndGames, SetNameForMicroSDCard } from "./hooks/backend"; import React from "react"; -import { CardAndGames, MicroSDEntryType } from "./lib/Types"; -import { EditCardModal } from "./modals/EditCardModal"; +import { CardAndGames, MicroSDCard, MicroSDEntryType } from "./lib/Types"; import DocumentationPage from "./pages/Docs"; import { DeckyAPI } from "./lib/DeckyApi"; +import { MicroSDeckContextProvider, useMicroSDeckContext } from "./state/MicroSDeckContext"; +import { MicroSDeckManager } from "./state/MicoSDeckManager"; +import { CardActionsContextMenu } from "./components/CardActions"; // function RenderCard({ data }: { data: CardAndGames }) { // Logger.Log("Rendering Card"); @@ -45,36 +43,33 @@ import { DeckyAPI } from "./lib/DeckyApi"; // // ) // } - -function EditCardButton({ card }: { card: CardAndGames }) { - - +interface EditCardButtonProps { + microSDeckManager: MicroSDeckManager, + currentCard: MicroSDCard | undefined, + cardAndGames: CardAndGames +} +function EditCardButton(props: EditCardButtonProps) { const onClick = () => { - showModal( { - Logger.Log("Changed Card {cardId} to name \"{name}\"", { cardId, name }); - SetNameForMicroSDCard(cardId, name); - }} - cardId={card[0].uid} - cardName={card[0].name} - />); + showContextMenu(); } return ( - + ) } -const Content: VFC<{ serverAPI: ServerAPI }> = ({ }) => { - const { cards, refresh } = GetCardsAndGames(); +function Content(){ + const {currentCardAndGames, cardsAndGames, microSDeckManager} = useMicroSDeckContext(); - const isLoaded = !!cards; + const [currentCard] = currentCardAndGames || [undefined]; + + const isLoaded = !!cardsAndGames; // const [selectedCard, setSelectedCard] = useState(0); @@ -88,16 +83,15 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({ }) => { // }) ?? [{ label: "Loading...", data: null } as DropdownOption]; - const entries = cards?.map(([card]) => { + const entries = cardsAndGames?.map(([card]) => { return { label: - -
-
- +
+
+
-
{card.name}
-
{card.uid}
+
{card.name || UNAMED_CARD_NAME}
+
{card.uid}
, position: card.position || 0, @@ -108,11 +102,10 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({ }) => { function CardInteractables({ entry }: { entry: ReorderableEntry }) { - const card = cards!.find(([card]) => card.uid == entry.data!.uid)!; - return (); + const cardAndGames = cardsAndGames!.find(([card]) => card.uid == entry.data!.uid)!; + return (); } - return ( <> { Navigation.CloseSideMenus(); Navigation.Navigate(DOCUMENTATION_PATH); }}> @@ -168,6 +161,11 @@ export default definePlugin((serverApi: ServerAPI) => { exact: true, }); + const microSDeckManager = new MicroSDeckManager(); + //@ts-ignore sssshhhhh + window.microSDeckManager = microSDeckManager; + microSDeckManager.init(); + DeckyAPI.SetApi(serverApi); const patch = PatchAppScreen(serverApi); @@ -176,7 +174,10 @@ export default definePlugin((serverApi: ServerAPI) => { return { title:
Example Plugin
, - content: , + content: + + + , icon: , onDismount() { serverApi.routerHook.removeRoute(DOCUMENTATION_PATH); diff --git a/src/lib/Types.ts b/src/lib/Types.ts index 93aa11d..a33c7aa 100644 --- a/src/lib/Types.ts +++ b/src/lib/Types.ts @@ -6,8 +6,8 @@ export type MicroSDCard = { uid: string, name: string, games: string[], - position: number | undefined, - hidden: boolean | undefined + position: number, + hidden: boolean, } @@ -15,9 +15,9 @@ export type Game = { uid: string, name: string, size: number, - card: string + is_steam: boolean, } export type CardAndGames = [MicroSDCard, Game[]]; -export type CardsAndGames = CardAndGames[]; \ No newline at end of file +export type CardsAndGames = CardAndGames[]; diff --git a/src/modals/EditCardModal.tsx b/src/modals/EditCardModal.tsx index a5bdc52..48e6dee 100644 --- a/src/modals/EditCardModal.tsx +++ b/src/modals/EditCardModal.tsx @@ -1,26 +1,34 @@ import { ConfirmModal, Field, + Focusable, + ScrollPanel, TextField, + ToggleField, quickAccessControlsClasses } from "decky-frontend-lib"; import React from "react"; import { useState, VFC, useEffect, } from "react"; import { DeckyAPI } from "../lib/DeckyApi"; +import { Game, MicroSDCard } from "../lib/Types"; +import { Logger } from "../Logging"; +import { UNAMED_CARD_NAME } from "../const"; type EditCardProps = { closeModal?: () => void, - onConfirm: (cardId: string, name: string) => void, - cardName: string | undefined, - cardId: string + onConfirm: (card: MicroSDCard) => void, + card: MicroSDCard, + games: Game[] }; /** * The modal for editing and creating custom tabs. */ -export const EditCardModal: VFC = ({ cardId, cardName, onConfirm, closeModal }) => { - const [name, setName] = useState(cardName ?? ''); +export const EditCardModal: VFC = ({ card, games, onConfirm, closeModal }) => { + + const [name, setName] = useState(card.name ?? ''); + const [hidden, setHidden] = useState(card.hidden); const [canSave, setCanSave] = useState(false); const nameInputElt = ; @@ -38,33 +46,42 @@ export const EditCardModal: VFC = ({ cardId, cardName, onConfirm, DeckyAPI.Toast("Cannot Save MicroSD Card", "You must provide a valid name"); return; } - onConfirm(cardId, name); + onConfirm({ ...card, name, hidden }); closeModal!(); } return ( - <> -
- -
- -
- Name -
- {nameInputElt} - - } /> + + +
+ Name +
+ {nameInputElt} + + } /> + { + Logger.Log("changed value to: {checked}", { checked }); + setHidden(checked); + }} /> + + +
+ Games
-
-
- +
    + { + games.map(v => (
  • {v.name}
  • )) + } +
+ + +
); -}; \ No newline at end of file +}; diff --git a/src/state/MicoSDeckManager.tsx b/src/state/MicoSDeckManager.tsx new file mode 100644 index 0000000..8e0f8ed --- /dev/null +++ b/src/state/MicoSDeckManager.tsx @@ -0,0 +1,51 @@ +import { Logger } from "../Logging"; +import { fetchCardsAndGames, fetchCurrentCardAndGames, fetchDeleteCard, fetchUpdateCard } from "../hooks/backend"; +import { CardAndGames, CardsAndGames, MicroSDCard } from "../lib/Types" + +export class MicroSDeckManager { + public eventBus = new EventTarget(); + + private currentCardAndGames: CardAndGames | undefined; + private cardsAndGames: CardsAndGames = []; + + + init() { + this.fetch(); + } + + async fetch() { + this.currentCardAndGames = await fetchCurrentCardAndGames(); + this.cardsAndGames = await fetchCardsAndGames() || []; + this.eventBus.dispatchEvent(new Event("stateUpdate")); + } + + getCardsAndGames() { + return { + cardsAndGames: this.cardsAndGames + } + } + + getCurrentCard() { + return { + currentCardAndGames: this.currentCardAndGames + } + } + + async updateCard(card: MicroSDCard) { + Logger.Debug("Updating card {uid}", card); + await fetchUpdateCard(card); + await this.fetch() + } + + async deleteCard(card: MicroSDCard) { + Logger.Log("Deleting Card {uid}", card); + await fetchDeleteCard(card); + await this.fetch(); + } + + async hideCard(card: MicroSDCard) { + card.hidden = true; + //TODO: Implement + Logger.Log("Card {uid} was supposed to be hidden", card); + } +} diff --git a/src/state/MicroSDeckContext.tsx b/src/state/MicroSDeckContext.tsx new file mode 100644 index 0000000..13c50b1 --- /dev/null +++ b/src/state/MicroSDeckContext.tsx @@ -0,0 +1,53 @@ +import { createContext, FC, useContext, useEffect, useState } from "react"; +import { MicroSDeckManager } from "./MicoSDeckManager"; +import { CardAndGames, CardsAndGames } from "../lib/Types"; + +const MicroSDeckContext = createContext(null as any); +export const useMicroSDeckContext = () => useContext(MicroSDeckContext); + +interface ProviderProps { + microSDeckManager: MicroSDeckManager +} + + +interface PublicMicroSDeckManager { + currentCardAndGames: CardAndGames | undefined; + cardsAndGames: CardsAndGames; +} + +interface MicroSDeckContext extends PublicMicroSDeckManager { + microSDeckManager: MicroSDeckManager +} + +export const MicroSDeckContextProvider: FC = ({ children, microSDeckManager }) => { + const [publicState, setPublicState] = useState({ + ...microSDeckManager.getCardsAndGames(), + ...microSDeckManager.getCurrentCard() + }); + + useEffect(() => { + function onUpdate() { + setPublicState({ + ...microSDeckManager.getCardsAndGames(), + ...microSDeckManager.getCurrentCard() + }); + } + + microSDeckManager.eventBus.addEventListener("stateUpdate", onUpdate); + + return () => { + microSDeckManager.eventBus.removeEventListener("stateUpdate", onUpdate); + } + }, []); + + return ( + + {children} + + ) +}