From c4b87009bef97280084f563a53ae31b03c7e9306 Mon Sep 17 00:00:00 2001 From: neoapps-dev Date: Thu, 27 Aug 2026 17:14:56 +0300 Subject: [PATCH] feat(plugins): custom splashes --- src/hooks/useAudioController.ts | 19 +++++++++++------ src/plugins/PluginAPI.ts | 3 +++ src/plugins/PluginManager.ts | 36 +++++++++++++++++++++++++++++++++ src/plugins/types.ts | 1 + 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/hooks/useAudioController.ts b/src/hooks/useAudioController.ts index 14cb900..38a2249 100644 --- a/src/hooks/useAudioController.ts +++ b/src/hooks/useAudioController.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useRef, useCallback } from "react"; -import { SPLASHES } from "../data/splashes"; +import { PluginManager } from "../plugins/PluginManager"; const TRACKS = [ "music/Moog City 2.opus", @@ -29,10 +29,16 @@ export function useAudioController({ isGameRunning, isWindowVisible, }: AudioControllerProps) { + const [splashes, setSplashes] = useState(() => PluginManager.instance.getSplashes()); const [currentTrack, setCurrentTrack] = useState(0); const [splashIndex, setSplashIndex] = useState( - () => Math.floor(Math.random() * SPLASHES.length), + () => Math.floor(Math.random() * PluginManager.instance.getSplashes().length), ); + + useEffect(() => { + PluginManager.instance.setSplashesChangedCallback((s) => setSplashes(s)); + setSplashes(PluginManager.instance.getSplashes()); + }, []); const audioContextRef = useRef(null); const musicSourceRef = useRef(null); const musicGainRef = useRef(null); @@ -235,11 +241,12 @@ export function useAudioController({ const cycleSplash = useCallback(() => { playSplashSound(); let newIndex; + if (splashes.length === 0) return; do { - newIndex = Math.floor(Math.random() * SPLASHES.length); - } while (newIndex === splashIndex && SPLASHES.length > 1); + newIndex = Math.floor(Math.random() * splashes.length); + } while (newIndex === splashIndex && splashes.length > 1); setSplashIndex(newIndex); - }, [playSplashSound, splashIndex]); + }, [playSplashSound, splashIndex, splashes]); const skipTrack = useCallback(() => { isManualSkipRef.current = true; setCurrentTrack((prev) => (prev + 1) % TRACKS.length); @@ -346,7 +353,7 @@ export function useAudioController({ playBackSound, playSfx, tracks: TRACKS, - splashes: SPLASHES, + splashes: splashes, startMusic, }; } diff --git a/src/plugins/PluginAPI.ts b/src/plugins/PluginAPI.ts index 8f73c39..0d362dd 100644 --- a/src/plugins/PluginAPI.ts +++ b/src/plugins/PluginAPI.ts @@ -189,6 +189,9 @@ export function buildPluginAPI( playSound(name: string): void { manager.playSound(name); }, + addSplash(splash: string): UnsubscribeFn { + return manager.registerSplash(pluginId, splash); + }, async openUrl(url: string): Promise { if ( perms.size > 0 && diff --git a/src/plugins/PluginManager.ts b/src/plugins/PluginManager.ts index 119cc52..7f55213 100644 --- a/src/plugins/PluginManager.ts +++ b/src/plugins/PluginManager.ts @@ -1,6 +1,7 @@ import { invoke } from "@tauri-apps/api/core"; import { buildPluginAPI } from "./PluginAPI"; import { PluginSandbox } from "./PluginSandbox"; +import { SPLASHES } from "../data/splashes"; import type { PluginManifest, LoadedPlugin, @@ -39,12 +40,14 @@ export class PluginManager { private hooks: Map> = new Map(); private views: Map = new Map(); private actions: Map> = new Map(); + private splashes: Map> = new Map(); private stateSubs: Map = new Map(); private pluginEvents: Map>> = new Map(); private onViewsChanged: ViewChangeCallback | null = null; private onNavigate: NavigateCallback | null = null; private onToast: ToastCallback | null = null; private onSound: SoundCallback | null = null; + private onSplashesChanged: ((splashes: string[]) => void) | null = null; private onEnabledChanged: (() => void) | null = null; private configSnapshot: Record = {}; private gameStateSnapshot: Record = {}; @@ -71,6 +74,7 @@ export class PluginManager { this.enabledMap.set(id, enabled); localStorage.setItem(`plugin:enabled:${id}`, String(enabled)); this.notifyViewsChanged(); + this.notifySplashesChanged(); this.onEnabledChanged?.(); } @@ -97,6 +101,10 @@ export class PluginManager { this.onSound = cb; } + setSplashesChangedCallback(cb: (splashes: string[]) => void): void { + this.onSplashesChanged = cb; + } + updateSnapshots( config: Record, game: Record, @@ -139,9 +147,11 @@ export class PluginManager { this.hooks.clear(); this.views.clear(); this.actions.clear(); + this.splashes.clear(); this.plugins.clear(); this.enabledMap.clear(); this.pluginEvents.clear(); + this.notifySplashesChanged(); await this.init(); } @@ -369,7 +379,33 @@ export class PluginManager { this.onSound?.(name); } + registerSplash(pluginId: string, splash: string): UnsubscribeFn { + if (!this.splashes.has(pluginId)) { + this.splashes.set(pluginId, new Set()); + } + this.splashes.get(pluginId)!.add(splash); + this.notifySplashesChanged(); + return () => { + this.splashes.get(pluginId)?.delete(splash); + this.notifySplashesChanged(); + }; + } + + getSplashes(): string[] { + const pluginSplashes: string[] = []; + this.splashes.forEach((set, pid) => { + if (this.isPluginEnabled(pid)) { + pluginSplashes.push(...set); + } + }); + return [...SPLASHES, ...pluginSplashes]; + } + private notifyViewsChanged(): void { this.onViewsChanged?.(this.getViews()); } + + private notifySplashesChanged(): void { + this.onSplashesChanged?.(this.getSplashes()); + } } diff --git a/src/plugins/types.ts b/src/plugins/types.ts index c636d05..905d682 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -134,6 +134,7 @@ export interface PluginAPI { ui: { showToast(message: string, options?: ToastOptions): void; playSound(name: string): void; + addSplash(splash: string): UnsubscribeFn; openUrl(url: string): Promise; }; log: {