diff --git a/CHANGELOG.md b/CHANGELOG.md index 6218b83..054a89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +## [3.18.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.4...v3.18.5) (2022-12-21) + + +### Bug Fixes + +* fixed prop interfaces ([#70](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/70)) ([0b50f2c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0b50f2cf0baa76fc00aa0a41a8435d7a512bff19)) + +## [3.18.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.3...v3.18.4) (2022-12-16) + + +### Bug Fixes + +* **modals:** fix ModalRoot again ([fd94842](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/fd94842647e51dd9a104e170e0c5ee2bebce12d6)) + +## [3.18.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.2...v3.18.3) (2022-12-12) + + +### Bug Fixes + +* **Router:** update Router interface to SteamOS3.4 and add Navigation ([#52](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/52)) ([f0379e5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/f0379e5d19279863b571e66918bc9107efedb612)) + +## [3.18.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.1...v3.18.2) (2022-12-11) + + +### Bug Fixes + +* **useQuickAccessVisible:** remove invalid prop access ([#66](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/66)) ([767dc2f](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/767dc2fcee97d8b6c2d331ae29704d9b469de51a)) + +## [3.18.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.0...v3.18.1) (2022-12-11) + + +### Bug Fixes + +* **findSP:** fallback to last active context ([#53](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/53)) ([6f14da1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/6f14da152acc4757b814844f1b77bf83dd98d77e)) + +# [3.18.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.17.0...v3.18.0) (2022-12-11) + + +### Features + +* **DialogCheckbox:** Add DialogCheckbox component ([#58](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/58)) ([88f245d](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/88f245d476a6477e9fc0cd35e9b675961ecbc26c)) + +# [3.17.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.16.2...v3.17.0) (2022-12-11) + + +### Features + +* **ControlsList:** Add ControlsList component ([#61](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/61)) ([c586afb](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/c586afb97d59928ecb703b5a254ed1b9405e2c7e)) + +## [3.16.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.16.1...v3.16.2) (2022-12-11) + + +### Bug Fixes + +* **Marquee:** replace default export with named export ([cd0635e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/cd0635e94f98499f9f5fc24a7fd4b93efe7dfc38)) + +## [3.16.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.16.0...v3.16.1) (2022-12-11) + + +### Bug Fixes + +* **FooterLegend:** change description types to ReactNode ([#62](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/62)) ([d24136e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/d24136ecb6b0c5239b68723e8f92a4822aa7b590)) + +# [3.16.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.15.0...v3.16.0) (2022-12-11) + + +### Features + +* **Marquee:** Add Marquee component ([#63](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/63)) ([925ea8c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/925ea8c3ceaaf6ff2f79b8808908a9b144a4fcff)) + +# [3.15.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.14.0...v3.15.0) (2022-12-11) + + +### Features + +* **Focusable:** add noFocusRing prop type ([#65](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/65)) ([cc29dda](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/cc29ddaf578e21ab2abe1cd266f1d15debee0637)) + +# [3.14.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.13.0...v3.14.0) (2022-12-10) + + +### Features + +* **toast:** add showToast/playSound to ToastData ([#64](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/64)) ([7ba1229](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7ba1229a4e24fea587b96dc8b078200faf45ddee)) + +# [3.13.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.12.0...v3.13.0) (2022-11-29) + + +### Features + +* **Menu:** add more missing props ([#60](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/60)) [CI SKIP] ([678ba21](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/678ba216f1e194986b0c391398e6f73536cd0102)) + +# [3.12.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.11.1...v3.12.0) (2022-11-28) + + +### Features + +* **MenuItem:** add missing props ([#59](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/59)) ([c84a091](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/c84a09146935f0942265b7a1e4aadc40e8cf22dc)) + ## [3.11.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.11.0...v3.11.1) (2022-11-20) diff --git a/package.json b/package.json index c3837da..ae8253e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-frontend-lib", - "version": "3.11.1", + "version": "3.18.5", "description": "A library for building decky plugins", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/custom-hooks/useQuickAccessVisible.tsx b/src/custom-hooks/useQuickAccessVisible.tsx index 5872b40..296e26b 100644 --- a/src/custom-hooks/useQuickAccessVisible.tsx +++ b/src/custom-hooks/useQuickAccessVisible.tsx @@ -6,7 +6,8 @@ declare global { function getQuickAccessWindow(): Window | null { try { - const navTrees = FocusNavController?.m_ActiveContext?.m_rgGamepadNavigationTrees || FocusNavController?.m_rgGamepadNavigationTrees; + const context = FocusNavController?.m_ActiveContext || FocusNavController?.m_LastActiveContext; + const navTrees = context?.m_rgGamepadNavigationTrees || FocusNavController?.m_rgGamepadNavigationTrees; return navTrees?.find((tree: any) => tree?.id === "QuickAccess-NA")?.m_Root?.m_element?.ownerDocument.defaultView ?? null; } catch (error) { console.error(error); diff --git a/src/deck-components/ControlsList.tsx b/src/deck-components/ControlsList.tsx new file mode 100644 index 0000000..8c974f4 --- /dev/null +++ b/src/deck-components/ControlsList.tsx @@ -0,0 +1,17 @@ +import { findModuleChild } from '../webpack'; +import { FC } from 'react'; + +export interface ControlsListProps { + alignItems?: 'left' | 'right' | 'center'; + spacing?: 'standard' | 'extra'; +} + +export const ControlsList: FC = findModuleChild((m) => { + if (typeof m !== 'object') return; + for (const prop in m) { + if (m[prop]?.toString && m[prop].toString().includes('().ControlsListChild') && m[prop].toString().includes('().ControlsListOuterPanel')) { + return m[prop]; + } + } + return; +}); diff --git a/src/deck-components/DialogCheckbox.tsx b/src/deck-components/DialogCheckbox.tsx new file mode 100644 index 0000000..b16068d --- /dev/null +++ b/src/deck-components/DialogCheckbox.tsx @@ -0,0 +1,33 @@ +import { FC, ReactNode } from 'react'; + +import { findModule } from '../webpack'; +import { DialogCommonProps } from './Dialog'; +import { FooterLegendProps } from './FooterLegend'; + +export interface DialogCheckboxProps extends DialogCommonProps, FooterLegendProps { + onChange?(checked: boolean): void; + label?: ReactNode; + description?: ReactNode; + disabled?: boolean; + tooltip?: string; + color?: string; + highlightColor?: string; + bottomSeparator?: 'standard' | 'thick' | 'none'; + controlled?: boolean; + checked?: boolean; + onClick?(evt: Event): void; +} + +export const DialogCheckbox = Object.values(findModule((m: any) => { + if (typeof m !== 'object') return false; + for (const prop in m) { + if (m[prop]?.prototype?.GetPanelElementProps) return true; + } + return false; +})).find((m: any) => + m.contextType && + m.prototype?.render.toString().includes('fallback:') && + m?.prototype?.SetChecked && + m?.prototype?.Toggle && + m?.prototype?.GetPanelElementProps +) as FC; diff --git a/src/deck-components/Focusable.tsx b/src/deck-components/Focusable.tsx index eb49d97..3ec8445 100644 --- a/src/deck-components/Focusable.tsx +++ b/src/deck-components/Focusable.tsx @@ -8,6 +8,7 @@ export interface FocusableProps extends HTMLAttributes, FooterLe 'flow-children'?: string; focusClassName?: string; focusWithinClassName?: string; + noFocusRing?: boolean; onActivate?: (e: CustomEvent) => void; onCancel?: (e: CustomEvent) => void; } diff --git a/src/deck-components/FooterLegend.ts b/src/deck-components/FooterLegend.ts index cc400cd..a9a67ad 100644 --- a/src/deck-components/FooterLegend.ts +++ b/src/deck-components/FooterLegend.ts @@ -1,3 +1,5 @@ +import { ReactNode } from 'react'; + export enum GamepadButton { INVALID, OK, @@ -42,16 +44,16 @@ export interface GamepadEventDetail { source: number; } export declare type ActionDescriptionMap = { - [key in GamepadButton]?: string + [key in GamepadButton]?: ReactNode } export declare type GamepadEvent = CustomEvent; export interface FooterLegendProps { actionDescriptionMap?: ActionDescriptionMap; - onOKActionDescription?: string; - onCancelActionDescription?: string; - onSecondaryActionDescription?: string; - onOptionsActionDescription?: string; - onMenuActionDescription?: string; + onOKActionDescription?: ReactNode; + onCancelActionDescription?: ReactNode; + onSecondaryActionDescription?: ReactNode; + onOptionsActionDescription?: ReactNode; + onMenuActionDescription?: ReactNode; onButtonDown?: (evt: GamepadEvent) => void; onButtonUp?: (evt: GamepadEvent) => void; onOKButton?: (evt: GamepadEvent) => void; diff --git a/src/deck-components/Marquee.tsx b/src/deck-components/Marquee.tsx new file mode 100644 index 0000000..b285fd7 --- /dev/null +++ b/src/deck-components/Marquee.tsx @@ -0,0 +1,26 @@ +import { CSSProperties, FC } from 'react'; + +import { findModuleChild } from '../webpack'; + +export interface MarqueeProps { + play?: boolean; + direction?: 'left' | 'right'; + speed?: number; + delay?: number; + fadeLength?: number; + center?: boolean; + resetOnPause?: boolean; + style?: CSSProperties; + className?: string; + children: React.ReactNode; +} + +export const Marquee: FC = findModuleChild((m) => { + if (typeof m !== 'object') return; + for (const prop in m) { + if (m[prop]?.toString && m[prop].toString().includes('.Marquee') && m[prop].toString().includes('--fade-length')) { + return m[prop]; + } + } + return; +}); diff --git a/src/deck-components/Menu.tsx b/src/deck-components/Menu.tsx index 7ce937a..afe1f25 100755 --- a/src/deck-components/Menu.tsx +++ b/src/deck-components/Menu.tsx @@ -2,6 +2,7 @@ import { FC, ReactNode } from 'react'; import { fakeRenderComponent } from '../utils'; import { findModuleChild } from '../webpack'; +import { FooterLegendProps } from './FooterLegend'; export const showContextMenu: (children: ReactNode, parent?: EventTarget) => void = findModuleChild((m) => { if (typeof m !== 'object') return undefined; @@ -12,7 +13,7 @@ export const showContextMenu: (children: ReactNode, parent?: EventTarget) => voi } }); -export interface MenuProps { +export interface MenuProps extends FooterLegendProps { label: string; onCancel?(): void; cancelText?: string; @@ -49,9 +50,16 @@ export const MenuGroup: FC = findModuleChild((m) => { } }); -export interface MenuItemProps { - onSelected?(): void; +export interface MenuItemProps extends FooterLegendProps { + bInteractableItem?: boolean; + onClick?(evt: Event): void; + onSelected?(evt: Event): void; + onMouseEnter?(evt: MouseEvent): void; + onMoveRight?(): void; + selected?: boolean; disabled?: boolean; + bPlayAudio?: boolean; + tone?: 'positive' | 'emphasis' | 'destructive'; children?: ReactNode; } diff --git a/src/deck-components/Modal.tsx b/src/deck-components/Modal.tsx index f0815c3..f01c5e7 100755 --- a/src/deck-components/Modal.tsx +++ b/src/deck-components/Modal.tsx @@ -38,7 +38,7 @@ const showModalRaw: unknown1?: unknown, hideActions?: { bHideActions?: boolean }, modalManager?: unknown, - ) => Promise) + ) => ShowModalResult) | void = findModuleChild((m) => { if (typeof m !== 'object') return undefined; for (let prop in m) { @@ -52,16 +52,15 @@ const showModalRaw: } }); -const oldShowModalRaw: - | ((modal: ReactNode, parent?: EventTarget, props?: ShowModalProps) => Promise) - | void = findModuleChild((m) => { - if (typeof m !== 'object') return undefined; - for (let prop in m) { - if (typeof m[prop] === 'function' && m[prop].toString().includes('bHideMainWindowForPopouts:!0')) { - return m[prop]; +const oldShowModalRaw: ((modal: ReactNode, parent?: EventTarget, props?: ShowModalProps) => ShowModalResult) | void = + findModuleChild((m) => { + if (typeof m !== 'object') return undefined; + for (let prop in m) { + if (typeof m[prop] === 'function' && m[prop].toString().includes('bHideMainWindowForPopouts:!0')) { + return m[prop]; + } } - } -}); + }); export const showModal = ( modal: ReactNode, @@ -70,7 +69,7 @@ export const showModal = ( strTitle: 'Decky Dialog', bHideMainWindowForPopouts: false, }, -): Promise => { +): ShowModalResult => { if (showModalRaw) { return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, { bHideActions: props.bHideActionIcons, @@ -118,13 +117,13 @@ export const ConfirmModal = findModuleChild((m) => { } }) as FC; -// new +// new as of december 2022 on beta export const ModalRoot = (Object.values( findModule((m: any) => { if (typeof m !== 'object') return false; for (let prop in m) { - if (m[prop]?.toString()?.includes('"ModalManager","DialogWrapper"')) { + if (m[prop]?.m_mapModalManager) { return true; } } @@ -132,6 +131,20 @@ export const ModalRoot = (Object.values( return false; }) || {}, )?.find((x: any) => x?.type?.toString()?.includes('((function(){')) || + // before december 2022 beta + Object.values( + findModule((m: any) => { + if (typeof m !== 'object') return false; + + for (let prop in m) { + if (m[prop]?.toString()?.includes('"ModalManager","DialogWrapper"')) { + return true; + } + } + + return false; + }) || {}, + )?.find((x: any) => x?.type?.toString()?.includes('((function(){')) || // old findModuleChild((m) => { if (typeof m !== 'object') return undefined; diff --git a/src/deck-components/Panel.tsx b/src/deck-components/Panel.tsx index 565370b..86c9d6d 100644 --- a/src/deck-components/Panel.tsx +++ b/src/deck-components/Panel.tsx @@ -1,10 +1,11 @@ -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import { findModuleChild } from '../webpack'; export interface PanelSectionProps { title?: string; spinner?: boolean; + children?: ReactNode } const [panelSection, mod] = findModuleChild((mod: any) => { @@ -18,6 +19,10 @@ const [panelSection, mod] = findModuleChild((mod: any) => { export const PanelSection = panelSection as FC; +export interface PanelSectionRowProps { + children?: ReactNode +} + export const PanelSectionRow = Object.values(mod).filter( (exp: any) => !exp?.toString()?.includes('.PanelSection'), -)[0] as FC; +)[0] as FC; diff --git a/src/deck-components/Router.tsx b/src/deck-components/Router.tsx index 53c7706..252aba2 100644 --- a/src/deck-components/Router.tsx +++ b/src/deck-components/Router.tsx @@ -63,28 +63,38 @@ export type AppOverview = { sort_as: string; }; -export interface Router { - CloseSideMenus(): void; - OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void; - GetQuickAccessTab(): QuickAccessTab; - Navigate(path: string): void; - NavigateBackOrOpenMenu(): void; - NavigateToAppProperties(): void; - NavigateToBugForum(): void; - NavigateToExternalWeb(url: string): void; - NavigateToHelp(): void; - NavigateToInvites(): void; - NavigateToRunningApp(replace?: boolean): void; - NavigateToStorage(): void; - NavigateToStore(): void; - NavigateToStoreApp(appId: number | string): void; - NavigateToStoreFreeToPlay(): void; - NavigateToStoreManual(): void; - NavigateToStoreNewReleases(): void; - NavigateToStoreOnSale(): void; - ToggleSideMenu(sideMenu: SideMenu): void; - CloseSideMenus(): void; +export interface MenuStore { OpenSideMenu(sideMenu: SideMenu): void; + OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void; + OpenMainMenu(): void; +} + +export interface WindowRouter { + BrowserWindow: Window; + MenuStore: MenuStore; + Navigate(path: string): void; + NavigateToChat(): void; + NavigateToSteamWeb(url: string): void; + NavigateBack(): void; + NavigateToWebRoute(unknown?: any, unknown2?: any): void; +} + +export interface WindowStore { + GamepadUIMainWindowInstance?: WindowRouter; // Current + SteamUIWindows: WindowRouter[]; + OverlayWindows: WindowRouter[]; // Used by desktop GamepadUI +} + +export interface Router { + WindowStore?: WindowStore; + CloseSideMenus(): void; + Navigate(path: string): void; + NavigateToAppProperties(): void; + NavigateToExternalWeb(url: string): void; + NavigateToInvites(): void; + NavigateToChat(): void; + NavigateToLibraryTab(): void; + NavigateToLayoutPreview(e: unknown): void; OpenPowerMenu(unknown?: any): void; get RunningApps(): AppOverview[]; get MainRunningApp(): AppOverview | undefined; @@ -96,3 +106,51 @@ export const Router = findModuleChild((m: Module) => { if (m[prop]?.Navigate && m[prop]?.NavigationManager) return m[prop]; } }) as Router; + +export interface Navigation { + Navigate(path: string): void; + NavigateBack(): void; + NavigateToAppProperties(): void; + NavigateToExternalWeb(url: string): void; + NavigateToInvites(): void; + NavigateToChat(): void; + NavigateToLibraryTab(): void; + NavigateToLayoutPreview(e: unknown): void; + NavigateToSteamWeb(url: string): void; + NavigateToWebRoute(unknown?: any, unknown2?: any): void; + OpenSideMenu(sideMenu: SideMenu): void; + OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void; + OpenMainMenu(): void; + OpenPowerMenu(unknown?: any): void; + CloseSideMenus(): void; +} + +export const Navigation = { + Navigate: Router.Navigate.bind(Router), + NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack.bind( + Router.WindowStore.GamepadUIMainWindowInstance, + ), + NavigateToAppProperties: Router.NavigateToAppProperties.bind(Router), + NavigateToExternalWeb: Router.NavigateToExternalWeb.bind(Router), + NavigateToInvites: Router.NavigateToInvites.bind(Router), + NavigateToChat: Router.NavigateToChat.bind(Router), + NavigateToLibraryTab: Router.NavigateToLibraryTab.bind(Router), + NavigateToLayoutPreview: Router.NavigateToLayoutPreview.bind(Router), + NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb.bind( + Router.WindowStore.GamepadUIMainWindowInstance, + ), + NavigateToWebRoute: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToWebRoute.bind( + Router.WindowStore.GamepadUIMainWindowInstance, + ), + OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu.bind( + Router.WindowStore.GamepadUIMainWindowInstance.MenuStore, + ), + OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu.bind( + Router.WindowStore.GamepadUIMainWindowInstance.MenuStore, + ), + OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu.bind( + Router.WindowStore.GamepadUIMainWindowInstance.MenuStore, + ), + CloseSideMenus: Router.CloseSideMenus.bind(Router), + OpenPowerMenu: Router.OpenPowerMenu.bind(Router), +} as Navigation; diff --git a/src/deck-components/index.ts b/src/deck-components/index.ts index 20fa08b..d235e0f 100755 --- a/src/deck-components/index.ts +++ b/src/deck-components/index.ts @@ -1,12 +1,15 @@ export * from './Button'; export * from './ButtonItem'; export * from './Carousel'; +export * from './ControlsList'; export * from './Dialog'; +export * from './DialogCheckbox'; export * from './Dropdown'; export * from './Field'; export * from './Focusable'; export * from './FocusRing'; export * from './FooterLegend'; +export * from './Marquee'; export * from './Menu'; export * from './Modal'; export * from './Panel'; diff --git a/src/plugin.tsx b/src/plugin.tsx index 986699a..200a1f5 100644 --- a/src/plugin.tsx +++ b/src/plugin.tsx @@ -42,6 +42,10 @@ export interface ToastData { contentClassName?: string; duration?: number; critical?: boolean; + eType?: number; + sound?: number; + playSound?: boolean; + showToast?: boolean; } export interface Toaster { diff --git a/src/utils/index.ts b/src/utils/index.ts index 713a006..d139e36 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -16,6 +16,7 @@ export function findSP(): Window { // old (SP as host) if (document.title == 'SP') return window; // new (SP as popup) - return FocusNavController.m_ActiveContext.m_rgGamepadNavigationTrees.find((x: any) => x.m_ID == 'root_1_').Root + const context = FocusNavController.m_ActiveContext || FocusNavController.m_LastActiveContext; + return context.m_rgGamepadNavigationTrees.find((x: any) => x.m_ID == 'root_1_').Root .Element.ownerDocument.defaultView; }