@decky/ui 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,8 @@ export interface ClassModule {
3
3
  [name: string]: string;
4
4
  }
5
5
  export declare const classModuleMap: Map<ModuleID, ClassModule>;
6
- export declare const classMap: IterableIterator<ClassModule>;
6
+ export declare const classMap: ClassModule[];
7
7
  export declare function findClass(id: string, name: string): string | void;
8
+ export declare function findClassByName(name: string): string | void;
8
9
  export declare function findClassModule(filter: (module: any) => boolean): ClassModule | void;
9
10
  export declare function unminifyClass(minifiedClass: string): string | void;
@@ -10,12 +10,15 @@ export const classModuleMap = createModuleMapping((m) => {
10
10
  }
11
11
  return false;
12
12
  });
13
- export const classMap = classModuleMap.values();
13
+ export const classMap = [...classModuleMap.values()];
14
14
  export function findClass(id, name) {
15
15
  return classModuleMap.get(id)?.[name];
16
16
  }
17
+ export function findClassByName(name) {
18
+ return classMap.find((m) => m[name])?.[name];
19
+ }
17
20
  export function findClassModule(filter) {
18
- return [...classModuleMap.values()].find((m) => filter(m));
21
+ return classMap.find((m) => filter(m));
19
22
  }
20
23
  export function unminifyClass(minifiedClass) {
21
24
  for (let m of classModuleMap.values()) {
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode } from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { FooterLegendProps } from './FooterLegend';
3
3
  export interface Tab {
4
4
  id: string;
@@ -13,4 +13,4 @@ export interface TabsProps {
13
13
  onShowTab: (tab: string) => void;
14
14
  autoFocusContents?: boolean;
15
15
  }
16
- export declare const Tabs: FC<TabsProps>;
16
+ export declare const Tabs: any;
@@ -1,48 +1,3 @@
1
- import { createElement, useEffect, useState } from 'react';
2
- import { fakeRenderComponent, findInReactTree, sleep } from '../utils';
3
1
  import { findModuleByExport } from '../webpack';
4
- import { SteamSpinner } from './SteamSpinner';
5
- let tabsComponent;
6
- const getTabs = async () => {
7
- if (tabsComponent)
8
- return tabsComponent;
9
- while (!window?.DeckyPluginLoader?.routerHook?.routes) {
10
- console.debug('[DFL:Tabs]: Waiting for Decky router...');
11
- await sleep(500);
12
- }
13
- return (tabsComponent = fakeRenderComponent(() => {
14
- return findInReactTree(findInReactTree(window.DeckyPluginLoader.routerHook.routes
15
- .find((x) => x.props.path == '/library/app/:appid/achievements')
16
- .props.children.type(), (x) => x?.props?.scrollTabsTop).type({ appid: 1 }), (x) => x?.props?.tabs).type;
17
- }, {
18
- useRef: () => ({ current: { reaction: { track: () => { } } } }),
19
- useContext: () => ({ match: { params: { appid: 1 } } }),
20
- useMemo: () => ({ data: {} }),
21
- }));
22
- };
23
- let oldTabs;
24
- try {
25
- const oldTabsModule = findModuleByExport((e) => e.Unbleed);
26
- if (oldTabsModule)
27
- oldTabs = Object.values(oldTabsModule).find((x) => x?.type?.toString()?.includes('((function(){'));
28
- }
29
- catch (e) {
30
- console.error('Error finding oldTabs:', e);
31
- }
32
- export const Tabs = (oldTabs ||
33
- ((props) => {
34
- const found = tabsComponent;
35
- const [tc, setTC] = useState(found);
36
- useEffect(() => {
37
- if (found)
38
- return;
39
- (async () => {
40
- console.debug('[DFL:Tabs]: Finding component...');
41
- const t = await getTabs();
42
- console.debug('[DFL:Tabs]: Found!');
43
- setTC(t);
44
- })();
45
- }, []);
46
- console.log('tc', tc);
47
- return tc ? createElement(tc, props) : window.SP_REACT.createElement(SteamSpinner, null);
48
- }));
2
+ const tabsModule = findModuleByExport(e => e?.toString()?.includes(".TabRowTabs") && e?.toString()?.includes("activeTab:"));
3
+ export const Tabs = tabsModule && Object.values(tabsModule).find((e) => e?.type?.toString()?.includes("((function()"));
@@ -1,3 +1,4 @@
1
+ import { WindowRouter } from '../modules/Router';
1
2
  import { AppDetails, LogoPosition, SteamAppOverview } from './SteamClient';
2
3
  declare global {
3
4
  interface Window {
@@ -46,5 +47,11 @@ declare global {
46
47
  GetCustomLogoPosition: (app: SteamAppOverview) => LogoPosition | null;
47
48
  SaveCustomLogoPosition: (app: SteamAppOverview, logoPositions: LogoPosition) => any;
48
49
  };
50
+ SteamUIStore: {
51
+ GetFocusedWindowInstance: () => WindowRouter;
52
+ };
53
+ securitystore: {
54
+ IsLockScreenActive: () => boolean;
55
+ };
49
56
  }
50
57
  }
@@ -1,4 +1,4 @@
1
- import { sleep } from '../utils';
1
+ import Logger from '../logger';
2
2
  import { findModuleExport } from '../webpack';
3
3
  export var SideMenu;
4
4
  (function (SideMenu) {
@@ -59,44 +59,53 @@ export var DisplayStatus;
59
59
  })(DisplayStatus || (DisplayStatus = {}));
60
60
  export const Router = findModuleExport((e) => e.Navigate && e.NavigationManager);
61
61
  export let Navigation = {};
62
+ const logger = new Logger("Navigation");
62
63
  try {
63
- (async () => {
64
- let InternalNavigators = {};
65
- if (!Router.NavigateToAppProperties || Router.deckyShim) {
66
- function initInternalNavigators() {
64
+ function createNavigationFunction(fncName, handler) {
65
+ return (...args) => {
66
+ let win;
67
+ try {
68
+ win = window.SteamUIStore.GetFocusedWindowInstance();
69
+ }
70
+ catch (e) {
71
+ logger.warn("Navigation interface failed to call GetFocusedWindowInstance", e);
72
+ }
73
+ if (!win) {
74
+ logger.warn("Navigation interface could not find any focused window. Falling back to GamepadUIMainWindowInstance");
75
+ win = Router.WindowStore?.GamepadUIMainWindowInstance;
76
+ }
77
+ if (win) {
67
78
  try {
68
- InternalNavigators = findModuleExport((e) => e.GetNavigator && e.SetNavigator)?.GetNavigator();
79
+ const thisObj = handler && handler(win);
80
+ (thisObj || win)[fncName](...args);
69
81
  }
70
82
  catch (e) {
71
- console.error('[DFL:Router]: Failed to init internal navigators, trying again');
83
+ logger.error("Navigation handler failed", e);
72
84
  }
73
85
  }
74
- initInternalNavigators();
75
- while (!InternalNavigators?.AppProperties) {
76
- console.log('[DFL:Router]: Trying to init internal navigators again');
77
- await sleep(2000);
78
- initInternalNavigators();
86
+ else {
87
+ logger.error("Navigation interface could not find a window to navigate");
79
88
  }
80
- }
81
- const newNavigation = {
82
- Navigate: Router.Navigate?.bind(Router),
83
- NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack?.bind(Router.WindowStore.GamepadUIMainWindowInstance),
84
- NavigateToAppProperties: InternalNavigators?.AppProperties || Router.NavigateToAppProperties?.bind(Router),
85
- NavigateToExternalWeb: InternalNavigators?.ExternalWeb || Router.NavigateToExternalWeb?.bind(Router),
86
- NavigateToInvites: InternalNavigators?.Invites || Router.NavigateToInvites?.bind(Router),
87
- NavigateToChat: InternalNavigators?.Chat || Router.NavigateToChat?.bind(Router),
88
- NavigateToLibraryTab: InternalNavigators?.LibraryTab || Router.NavigateToLibraryTab?.bind(Router),
89
- NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
90
- NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb?.bind(Router.WindowStore.GamepadUIMainWindowInstance),
91
- OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu?.bind(Router.WindowStore.GamepadUIMainWindowInstance.MenuStore),
92
- OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu?.bind(Router.WindowStore.GamepadUIMainWindowInstance.MenuStore),
93
- OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu?.bind(Router.WindowStore.GamepadUIMainWindowInstance.MenuStore),
94
- CloseSideMenus: Router.CloseSideMenus?.bind(Router),
95
- OpenPowerMenu: Router.OpenPowerMenu?.bind(Router),
96
89
  };
97
- Object.assign(Navigation, newNavigation);
98
- })();
90
+ }
91
+ const newNavigation = {
92
+ Navigate: createNavigationFunction("Navigate"),
93
+ NavigateBack: createNavigationFunction("NavigateBack"),
94
+ NavigateToAppProperties: createNavigationFunction("AppProperties", win => win.Navigator),
95
+ NavigateToExternalWeb: createNavigationFunction("ExternalWeb", win => win.Navigator),
96
+ NavigateToInvites: createNavigationFunction("Invites", win => win.Navigator),
97
+ NavigateToChat: createNavigationFunction("Chat", win => win.Navigator),
98
+ NavigateToLibraryTab: createNavigationFunction("LibraryTab", win => win.Navigator),
99
+ NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
100
+ NavigateToSteamWeb: createNavigationFunction("NavigateToSteamWeb"),
101
+ OpenSideMenu: createNavigationFunction("OpenSideMenu", win => win.MenuStore),
102
+ OpenQuickAccessMenu: createNavigationFunction("OpenQuickAccessMenu", win => win.MenuStore),
103
+ OpenMainMenu: createNavigationFunction("OpenMainMenu", win => win.MenuStore),
104
+ CloseSideMenus: createNavigationFunction("CloseSideMenus", win => win.MenuStore),
105
+ OpenPowerMenu: Router.OpenPowerMenu?.bind(Router),
106
+ };
107
+ Object.assign(Navigation, newNavigation);
99
108
  }
100
109
  catch (e) {
101
- console.error('[DFL:Router]: Error initializing Navigation interface', e);
110
+ logger.error('Error initializing Navigation interface', e);
102
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decky/ui",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "description": "A library for interacting with the Steam frontend in Decky plugins and elsewhere.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,15 +17,18 @@ export const classModuleMap: Map<ModuleID, ClassModule> = createModuleMapping((m
17
17
  return false;
18
18
  });
19
19
 
20
- export const classMap = classModuleMap.values();
20
+ export const classMap = [...classModuleMap.values()];
21
21
 
22
22
  export function findClass(id: string, name: string): string | void {
23
23
  return classModuleMap.get(id)?.[name];
24
24
  }
25
25
 
26
+ export function findClassByName(name: string): string | void {
27
+ return classMap.find((m) => m[name])?.[name];
28
+ }
29
+
26
30
  export function findClassModule(filter: (module: any) => boolean): ClassModule | void {
27
- // TODO optimize
28
- return [...classModuleMap.values()].find((m) => filter(m));
31
+ return classMap.find((m) => filter(m));
29
32
  }
30
33
 
31
34
  export function unminifyClass(minifiedClass: string): string | void {
@@ -1,9 +1,6 @@
1
- import { FC, ReactNode, createElement, useEffect, useState } from 'react';
2
-
3
- import { fakeRenderComponent, findInReactTree, sleep } from '../utils';
4
- import { Export, findModuleByExport } from '../webpack';
1
+ import { FC, ReactNode } from 'react';
2
+ import { findModuleByExport } from '../webpack';
5
3
  import { FooterLegendProps } from './FooterLegend';
6
- import { SteamSpinner } from './SteamSpinner';
7
4
 
8
5
  /**
9
6
  * Individual tab objects for the Tabs component
@@ -65,63 +62,9 @@ export interface TabsProps {
65
62
  autoFocusContents?: boolean;
66
63
  }
67
64
 
68
- let tabsComponent: any;
69
-
70
- const getTabs = async () => {
71
- if (tabsComponent) return tabsComponent;
72
- // @ts-ignore
73
- while (!window?.DeckyPluginLoader?.routerHook?.routes) {
74
- console.debug('[DFL:Tabs]: Waiting for Decky router...');
75
- await sleep(500);
76
- }
77
- return (tabsComponent = fakeRenderComponent(
78
- () => {
79
- return findInReactTree(
80
- findInReactTree(
81
- // @ts-ignore
82
- window.DeckyPluginLoader.routerHook.routes
83
- .find((x: any) => x.props.path == '/library/app/:appid/achievements')
84
- .props.children.type(),
85
- (x) => x?.props?.scrollTabsTop,
86
- ).type({ appid: 1 }),
87
- (x) => x?.props?.tabs,
88
- ).type;
89
- },
90
- {
91
- useRef: () => ({ current: { reaction: { track: () => {} } } }),
92
- useContext: () => ({ match: { params: { appid: 1 } } }),
93
- useMemo: () => ({ data: {} }),
94
- },
95
- ));
96
- };
97
-
98
- let oldTabs: any;
99
-
100
- try {
101
- const oldTabsModule = findModuleByExport((e: Export) => e.Unbleed);
102
- if (oldTabsModule)
103
- oldTabs = Object.values(oldTabsModule).find((x: any) => x?.type?.toString()?.includes('((function(){'));
104
- } catch (e) {
105
- console.error('Error finding oldTabs:', e);
106
- }
65
+ const tabsModule = findModuleByExport(e => e?.toString()?.includes(".TabRowTabs") && e?.toString()?.includes("activeTab:"));
107
66
 
108
67
  /**
109
68
  * Tabs component as used in the library and media tabs. See {@link TabsProps}.
110
- * Unlike other components in `decky-frontend-lib`, this requires Decky Loader to be running.
111
69
  */
112
- export const Tabs = (oldTabs ||
113
- ((props: TabsProps) => {
114
- const found = tabsComponent;
115
- const [tc, setTC] = useState<FC<TabsProps>>(found);
116
- useEffect(() => {
117
- if (found) return;
118
- (async () => {
119
- console.debug('[DFL:Tabs]: Finding component...');
120
- const t = await getTabs();
121
- console.debug('[DFL:Tabs]: Found!');
122
- setTC(t);
123
- })();
124
- }, []);
125
- console.log('tc', tc);
126
- return tc ? createElement(tc, props) : <SteamSpinner />;
127
- })) as FC<TabsProps>;
70
+ export const Tabs = tabsModule && Object.values(tabsModule).find((e: any) => e?.type?.toString()?.includes("((function()")) as FC<TabsProps>;
@@ -11,12 +11,14 @@ function getQuickAccessWindow(): Window | null {
11
11
 
12
12
  /**
13
13
  * Returns state indicating the visibility of quick access menu.
14
+ *
15
+ * @deprecated moved to @decky/api
14
16
  *
15
17
  * @returns `true` if quick access menu is visible and `false` otherwise.
16
18
  *
17
19
  * @example
18
20
  * import { FC, useEffect } from "react";
19
- * import { useQuickAccessVisible } from "decky-frontend-lib";
21
+ * import { useQuickAccessVisible } from "@decky/ui";
20
22
  *
21
23
  * export const PluginPanelView: FC<{}> = ({ }) => {
22
24
  * const isVisible = useQuickAccessVisible();
@@ -1,3 +1,4 @@
1
+ import { WindowRouter } from '../modules/Router';
1
2
  import { AppDetails, LogoPosition, SteamAppOverview } from './SteamClient';
2
3
  declare global {
3
4
  interface Window {
@@ -46,5 +47,11 @@ declare global {
46
47
  GetCustomLogoPosition: (app: SteamAppOverview) => LogoPosition | null;
47
48
  SaveCustomLogoPosition: (app: SteamAppOverview, logoPositions: LogoPosition) => any;
48
49
  };
50
+ SteamUIStore: {
51
+ GetFocusedWindowInstance: () => WindowRouter;
52
+ };
53
+ securitystore: {
54
+ IsLockScreenActive: () => boolean;
55
+ };
49
56
  }
50
57
  }
@@ -1,4 +1,4 @@
1
- import { sleep } from '../utils';
1
+ import Logger from '../logger';
2
2
  import { Export, findModuleExport } from '../webpack';
3
3
 
4
4
  export enum SideMenu {
@@ -88,14 +88,23 @@ export interface WindowStore {
88
88
 
89
89
  export interface Router {
90
90
  WindowStore?: WindowStore;
91
+ /** @deprecated use {@link Navigation} instead */
91
92
  CloseSideMenus(): void;
93
+ /** @deprecated use {@link Navigation} instead */
92
94
  Navigate(path: string): void;
95
+ /** @deprecated use {@link Navigation} instead */
93
96
  NavigateToAppProperties(): void;
97
+ /** @deprecated use {@link Navigation} instead */
94
98
  NavigateToExternalWeb(url: string): void;
99
+ /** @deprecated use {@link Navigation} instead */
95
100
  NavigateToInvites(): void;
101
+ /** @deprecated use {@link Navigation} instead */
96
102
  NavigateToChat(): void;
103
+ /** @deprecated use {@link Navigation} instead */
97
104
  NavigateToLibraryTab(): void;
105
+ /** @deprecated use {@link Navigation} instead */
98
106
  NavigateToLayoutPreview(e: unknown): void;
107
+ /** @deprecated use {@link Navigation} instead */
99
108
  OpenPowerMenu(unknown?: any): void;
100
109
  get RunningApps(): AppOverview[];
101
110
  get MainRunningApp(): AppOverview | undefined;
@@ -122,53 +131,52 @@ export interface Navigation {
122
131
 
123
132
  export let Navigation = {} as Navigation;
124
133
 
134
+ const logger = new Logger("Navigation");
135
+
125
136
  try {
126
- (async () => {
127
- let InternalNavigators: any = {};
128
- if (!Router.NavigateToAppProperties || (Router as unknown as any).deckyShim) {
129
- function initInternalNavigators() {
137
+ function createNavigationFunction(fncName: string, handler?: (win: any) => any) {
138
+ return (...args: any) => {
139
+ let win: WindowRouter | undefined;
140
+ try {
141
+ win = window.SteamUIStore.GetFocusedWindowInstance();
142
+ } catch (e) {
143
+ logger.warn("Navigation interface failed to call GetFocusedWindowInstance", e);
144
+ }
145
+ if (!win) {
146
+ logger.warn("Navigation interface could not find any focused window. Falling back to GamepadUIMainWindowInstance");
147
+ win = Router.WindowStore?.GamepadUIMainWindowInstance;
148
+ }
149
+
150
+ if (win) {
130
151
  try {
131
- InternalNavigators = findModuleExport((e: Export) => e.GetNavigator && e.SetNavigator)?.GetNavigator();
152
+ const thisObj = handler && handler(win);
153
+ (thisObj || win)[fncName](...args);
132
154
  } catch (e) {
133
- console.error('[DFL:Router]: Failed to init internal navigators, trying again');
155
+ logger.error("Navigation handler failed", e);
134
156
  }
135
- }
136
- initInternalNavigators();
137
- while (!InternalNavigators?.AppProperties) {
138
- console.log('[DFL:Router]: Trying to init internal navigators again');
139
- await sleep(2000);
140
- initInternalNavigators();
157
+ } else {
158
+ logger.error("Navigation interface could not find a window to navigate");
141
159
  }
142
160
  }
143
- const newNavigation = {
144
- Navigate: Router.Navigate?.bind(Router),
145
- NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack?.bind(
146
- Router.WindowStore.GamepadUIMainWindowInstance,
147
- ),
148
- NavigateToAppProperties: InternalNavigators?.AppProperties || Router.NavigateToAppProperties?.bind(Router),
149
- NavigateToExternalWeb: InternalNavigators?.ExternalWeb || Router.NavigateToExternalWeb?.bind(Router),
150
- NavigateToInvites: InternalNavigators?.Invites || Router.NavigateToInvites?.bind(Router),
151
- NavigateToChat: InternalNavigators?.Chat || Router.NavigateToChat?.bind(Router),
152
- NavigateToLibraryTab: InternalNavigators?.LibraryTab || Router.NavigateToLibraryTab?.bind(Router),
153
- NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
154
- NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb?.bind(
155
- Router.WindowStore.GamepadUIMainWindowInstance,
156
- ),
157
- OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu?.bind(
158
- Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
159
- ),
160
- OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu?.bind(
161
- Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
162
- ),
163
- OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu?.bind(
164
- Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
165
- ),
166
- CloseSideMenus: Router.CloseSideMenus?.bind(Router),
167
- OpenPowerMenu: Router.OpenPowerMenu?.bind(Router),
168
- } as Navigation;
161
+ }
162
+ const newNavigation = {
163
+ Navigate: createNavigationFunction("Navigate"),
164
+ NavigateBack: createNavigationFunction("NavigateBack"),
165
+ NavigateToAppProperties: createNavigationFunction("AppProperties", win => win.Navigator),
166
+ NavigateToExternalWeb: createNavigationFunction("ExternalWeb", win => win.Navigator),
167
+ NavigateToInvites: createNavigationFunction("Invites", win => win.Navigator),
168
+ NavigateToChat: createNavigationFunction("Chat", win => win.Navigator),
169
+ NavigateToLibraryTab: createNavigationFunction("LibraryTab", win => win.Navigator),
170
+ NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
171
+ NavigateToSteamWeb: createNavigationFunction("NavigateToSteamWeb"),
172
+ OpenSideMenu: createNavigationFunction("OpenSideMenu", win => win.MenuStore),
173
+ OpenQuickAccessMenu: createNavigationFunction("OpenQuickAccessMenu", win => win.MenuStore),
174
+ OpenMainMenu: createNavigationFunction("OpenMainMenu", win => win.MenuStore),
175
+ CloseSideMenus: createNavigationFunction("CloseSideMenus", win => win.MenuStore),
176
+ OpenPowerMenu: Router.OpenPowerMenu?.bind(Router),
177
+ } as Navigation;
169
178
 
170
- Object.assign(Navigation, newNavigation);
171
- })();
179
+ Object.assign(Navigation, newNavigation);
172
180
  } catch (e) {
173
- console.error('[DFL:Router]: Error initializing Navigation interface', e);
181
+ logger.error('Error initializing Navigation interface', e);
174
182
  }