Skip to content

Commit

Permalink
Renamed MicroSDeck class and cleaned up SSE
Browse files Browse the repository at this point in the history
  • Loading branch information
CEbbinghaus committed Nov 20, 2023
1 parent 1ae8e9b commit ea88921
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 84 deletions.
1 change: 0 additions & 1 deletion backend/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use actix_web::{
Either, HttpResponse, HttpResponseBuilder, Responder, Result,
};
use futures::StreamExt;
use log::debug;
use serde::Deserialize;
use std::{ops::Deref, sync::Arc};
use tokio::sync::broadcast::Sender;
Expand Down
6 changes: 3 additions & 3 deletions backend/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ pub enum CardEvent {
impl EventTrait for CardEvent {
fn get_event(&self) -> Option<&'static str> {
Some(match self {
CardEvent::Inserted => "Inserted",
CardEvent::Removed => "Removed",
CardEvent::Updated => "Updated",
CardEvent::Inserted => "insert",
CardEvent::Removed => "remove",
CardEvent::Updated => "update",
})
}
// fn get_data(&self) -> Option<&'static str> {
Expand Down
42 changes: 29 additions & 13 deletions lib/src/MicoSDeckManager.ts → lib/src/MicoSDeck.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { Event, fetchCardsAndGames, fetchCardsForGame, fetchCurrentCardAndGames, fetchDeleteCard, fetchEventTarget, fetchHealth, fetchUpdateCard, fetchVersion } from "./backend.js";
import { Event as BackendEvent, EventType, fetchCardsAndGames, fetchCardsForGame, fetchCurrentCardAndGames, fetchDeleteCard, fetchEventTarget, fetchHealth, fetchUpdateCard, fetchVersion } from "./backend.js";
import Logger from "lipe";
import { CardAndGames, CardsAndGames, MicroSDCard } from "./types.js"

function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(() => resolve(), ms));
}

export class MicroSDeckManager {
interface EventBusListener<T extends Event> {
(evt: T): void;
}

interface EventBusListenerObject<T extends Event> {
handleEvent(object: T): void;
}

export type EventBus<K extends string, T extends Event> = {
addEventListener(type: K, callback: EventBusListener<T> | EventBusListenerObject<T> | null, options?: boolean | AddEventListenerOptions | undefined): void;
dispatchEvent(event: T): boolean;
removeEventListener(type: K, callback: EventBusListener<T> | EventBusListenerObject<T> | null, options?: boolean | EventListenerOptions | undefined): void;
}

export class MicroSDeck {
private abortController = new AbortController();

private logger: Logger | undefined;
private fetchProps!: { url: string, logger?: Logger | undefined };

public eventBus = new EventTarget();
public eventBus = new EventTarget() as unknown as EventBus<EventType, Event | CustomEvent<BackendEvent>>;

private enabled: boolean = false;
public get Enabled() {
Expand All @@ -35,7 +49,9 @@ export class MicroSDeckManager {
constructor(props: { logger?: Logger, url: string }) {
this.logger = props.logger;

this.logger?.Log("Initializing MicroSDeckManager");
this.eventBus.addEventListener

this.logger?.Log("Initializing MicroSDeck");

this.fetchProps = props;

Expand All @@ -47,7 +63,7 @@ export class MicroSDeckManager {
this.logger?.Debug("Deconstruct Called");
if (this.isDestructed) return;
this.isDestructed = true;
this.logger?.Log("Deinitializing MicroSDeckManager");
this.logger?.Log("Deinitializing MicroSDeck");
this.abortController.abort("destruct");
}

Expand Down Expand Up @@ -79,11 +95,6 @@ export class MicroSDeckManager {
private async subscribeToUpdates() {
let signal = this.abortController.signal;

const handleCallback = async (event: string, data: Event) => {
await this.fetch();
this.eventBus.dispatchEvent(new CustomEvent(event, { detail: data }));
}

let sleepDelay = 5000;

if (this.pollLock !== undefined) {
Expand All @@ -104,10 +115,10 @@ export class MicroSDeckManager {

this.logger?.Debug("Poll listen");

await new Promise(async (res, rej) => {
await new Promise(async (res) => {
await sleep(sleepDelay);
fetchEventTarget({ ...this.fetchProps, callback: handleCallback }, { signal })

fetchEventTarget({ ...this.fetchProps, callback: this.handleCallback.bind(this) }, { signal })
.catch((reason) => {
this.logger?.Warn(`Listen was aborted with reason "${reason}"`);
res(0);
Expand All @@ -120,6 +131,11 @@ export class MicroSDeckManager {

}

async handleCallback(event: EventType, data?: BackendEvent) {
await this.fetch();
this.eventBus.dispatchEvent(new CustomEvent(event, { detail: data }));
}

async updateCard(card: MicroSDCard) {
this.logger?.Debug("Updating card {uid}", card);
await fetchUpdateCard({ ...this.fetchProps, card });
Expand Down
60 changes: 19 additions & 41 deletions lib/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ async function wrapFetch({ url, logger }: FetchProps, init?: RequestInit): Promi
return undefined;
}

export type EventType = "start" | "close" | "abort" | "message" | "insert" | "remove" | "update" | "change";
export type Event = {
event: string,
event: EventType,
data?: string,
id?: string
}

function decodeEvent(event: string): Event {
console.log(`Recieved event to process: [${event}]`);
function decodeEvent(event: string, logger?: Logger): Event {
logger?.Debug(`Recieved event to process: [{event}]`, {event});

var result = { event: "message" };
var result = { event: "message" as EventType };
var lines = event.split('\n');

for (let line of lines) {
Expand All @@ -56,7 +57,7 @@ function decodeEvent(event: string): Event {
return result;
}

function eventDecodeStream() {
function decodeStreamEvents(logger?: Logger) {
let buffer = "";
let pos = 0;
return new TransformStream<string, Event>({
Expand All @@ -73,60 +74,37 @@ function eventDecodeStream() {
pos = 0;

if (message) {
controller.enqueue(decodeEvent(message));
controller.enqueue(decodeEvent(message, logger));
}
}
}
})
}



function makeWriteableEventStream(eventTarget: EventTarget) {
export type EventCallback = (event: EventType, data?: Event) => any;
function makeCallbackEventStream(callback: EventCallback) {
return new WritableStream<Event>({
start() {
eventTarget.dispatchEvent(new Event('start'))
callback("start");
},
write(message) {
eventTarget.dispatchEvent(new CustomEvent(message.event, { detail: message }));
callback(message.event, message);
callback("change");
},
close() {
eventTarget.dispatchEvent(new CloseEvent('close'));
callback("close");
},
abort(reason) {
eventTarget.dispatchEvent(new CloseEvent('abort', { reason }));
abort() {
callback("abort");
}
})
}

export type EventCallback = (event: string, data: Event) => any;
function makeCallbackEventStream(callback: EventCallback) {
return new WritableStream<Event>({
start() { },
write(message) {
callback(message.event, message);
},
})
}

type EventTargetSink = { target: EventTarget };
type CallbackSink = { callback: EventCallback };

function isEventTargetSink(sink: EventTargetSink | CallbackSink): sink is EventTargetSink {
return (sink as EventTargetSink).target !== undefined;
}
export async function fetchEventTarget({url, logger, callback}: FetchProps & { callback: EventCallback }, init?: RequestInit) {
const eventDecoder = decodeStreamEvents(logger);
const outStream = makeCallbackEventStream(callback);

function determineOutputSink(sink: EventTargetSink | CallbackSink) {
if(isEventTargetSink(sink)) {
return makeWriteableEventStream(sink.target);
} else {
return makeCallbackEventStream(sink.callback);
}
}
export async function fetchEventTarget(props: FetchProps & (EventTargetSink | CallbackSink), init?: RequestInit) {
const eventDecoder = eventDecodeStream()
const outStream = determineOutputSink(props);
await fetch(`${props.url}/listen`, {...init, keepalive: true})
await fetch(`${url}/listen`, {...init, keepalive: true})
.then(response => {
response.body?.pipeThrough(new TextDecoderStream())
.pipeThrough(eventDecoder)
Expand Down
24 changes: 12 additions & 12 deletions lib/src/components/MicroSDeckContext.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import { createContext, useContext, useEffect, useState } from "react";
import { MicroSDeckManager } from "../MicoSDeckManager.js";
import { MicroSDeck } from "../MicoSDeck.js";
import { CardAndGames, CardsAndGames } from "../types.js";

const MicroSDeckContext = createContext<MicroSDeckContext>(null as any);
export const useMicroSDeckContext = () => useContext(MicroSDeckContext);

interface ProviderProps {
microSDeckManager: MicroSDeckManager
microSDeck: MicroSDeck
}

interface PublicMicroSDeckManager {
interface PublicMicroSDeck {
currentCardAndGames: CardAndGames | undefined;
cardsAndGames: CardsAndGames;
}

interface MicroSDeckContext extends PublicMicroSDeckManager {
microSDeckManager: MicroSDeckManager
interface MicroSDeckContext extends PublicMicroSDeck {
microSDeck: MicroSDeck
}

export function MicroSDeckContextProvider({ children, microSDeckManager }: React.PropsWithChildren<ProviderProps>) {
const [publicState, setPublicState] = useState<PublicMicroSDeckManager>({
...microSDeckManager.getProps()
export function MicroSDeckContextProvider({ children, microSDeck }: React.PropsWithChildren<ProviderProps>) {
const [publicState, setPublicState] = useState<PublicMicroSDeck>({
...microSDeck.getProps()
});

useEffect(() => {
function onUpdate() {
setPublicState({
...microSDeckManager.getProps()
...microSDeck.getProps()
});
}

microSDeckManager.eventBus.addEventListener("update", onUpdate);
microSDeck.eventBus.addEventListener("update", onUpdate);

return () => {
microSDeckManager.eventBus.removeEventListener("update", onUpdate);
microSDeck.eventBus.removeEventListener("update", onUpdate);
}
}, []);

return (
<MicroSDeckContext.Provider
value={{
...publicState,
microSDeckManager
microSDeck: microSDeck
}}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./types.js";
export * from "./hooks.js";
export * from "./MicoSDeckManager.js"
export * from "./MicoSDeck.js"
export * from "./components/MicroSDeckContext.js"
12 changes: 6 additions & 6 deletions src/components/CardActions.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { MenuItem, showModal, Menu, ConfirmModal } from "decky-frontend-lib"
import { CardAndGames, MicroSDCard, MicroSDeckManager } from "../../lib/src"
import { CardAndGames, MicroSDCard, MicroSDeck } from "../../lib/src"
import { EditCardModal } from "../modals/EditCardModal";
import { API_URL, UNAMED_CARD_NAME } from "../const";
import { GamesOnCardModal } from '../modals/GamesOnCardModal';
import { Logger } from '../Logging';

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

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

return (
Expand All @@ -30,7 +30,7 @@ export function CardActionsContextMenu({ cardAndGames, currentCard, microSDeckMa
<MenuItem onSelected={() => {
showModal(<EditCardModal
onConfirm={(card: MicroSDCard, nonSteamAdditions: string[], nonSteamDeletions: string[]) => {
microSDeckManager.updateCard(card);
microSDeck.updateCard(card);

//* probably want to move this into another method of microSDeckManager or combine it all into updateCard
nonSteamAdditions.forEach(async appId => {
Expand Down Expand Up @@ -72,14 +72,14 @@ export function CardActionsContextMenu({ cardAndGames, currentCard, microSDeckMa
}}>
Edit
</MenuItem>
<MenuItem onSelected={() => microSDeckManager.hideCard(card)}>
<MenuItem onSelected={() => microSDeck.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)}
onOK={() => microSDeck.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>);
Expand Down
14 changes: 7 additions & 7 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Logger } from "./Logging";
import React from "react";
import DocumentationPage from "./pages/Docs";
import { DeckyAPI } from "./lib/DeckyApi";
import { MicroSDeckManager, MicroSDeckContextProvider, useMicroSDeckContext, CardAndGames, MicroSDCard, MicroSDEntryType } from "../lib/src";
import { MicroSDeck, MicroSDeckContextProvider, useMicroSDeckContext, CardAndGames, MicroSDCard } from "../lib/src";
import { CardActionsContextMenu } from "./components/CardActions";
import { fetchUpdateCards } from "../lib/src/backend";

Expand Down Expand Up @@ -45,7 +45,7 @@ declare global {
// )
// }
interface EditCardButtonProps {
microSDeckManager: MicroSDeckManager,
microSDeck: MicroSDeck,
currentCard: MicroSDCard | undefined,
cardAndGames: CardAndGames
}
Expand All @@ -66,7 +66,7 @@ function EditCardButton(props: EditCardButtonProps) {
}

function Content() {
const { currentCardAndGames, cardsAndGames, microSDeckManager } = useMicroSDeckContext();
const { currentCardAndGames, cardsAndGames, microSDeck } = useMicroSDeckContext();

const [currentCard] = currentCardAndGames || [undefined];

Expand Down Expand Up @@ -104,7 +104,7 @@ function Content() {
entry: ReorderableEntry<MicroSDCard>
}) {
const cardAndGames = cardsAndGames!.find(([card]) => card.uid == entry.data!.uid)!;
return (<EditCardButton {...{ cardAndGames, currentCard, microSDeckManager }} />);
return (<EditCardButton {...{ cardAndGames, currentCard, microSDeck: microSDeck }} />);
}

return (
Expand Down Expand Up @@ -164,7 +164,7 @@ function Content() {
};

declare global {
var MicroSDeck: MicroSDeckManager | undefined;
var MicroSDeck: MicroSDeck | undefined;
}

export default definePlugin((serverApi: ServerAPI) => {
Expand All @@ -175,7 +175,7 @@ export default definePlugin((serverApi: ServerAPI) => {
if (window.MicroSDeck) {
window.MicroSDeck.destruct();
}
window.MicroSDeck = new MicroSDeckManager({ url: API_URL, logger: Logger });
window.MicroSDeck = new MicroSDeck({ url: API_URL, logger: Logger });

DeckyAPI.SetApi(serverApi);

Expand All @@ -186,7 +186,7 @@ export default definePlugin((serverApi: ServerAPI) => {
return {
title: <div className={staticClasses.Title}>Example Plugin</div>,
content:
<MicroSDeckContextProvider microSDeckManager={window.MicroSDeck}>
<MicroSDeckContextProvider microSDeck={window.MicroSDeck}>
<Content />
</MicroSDeckContextProvider>,
icon: <FaSdCard />,
Expand Down

0 comments on commit ea88921

Please sign in to comment.