Compare commits

...

11 Commits

Author SHA1 Message Date
semantic-release-bot
b5dc08a977 chore(release): 3.3.2 [CI SKIP] 2022-10-02 02:31:09 +00:00
AAGaming
40871af853 fix(modal): allow children 2022-10-01 22:30:33 -04:00
Lukas Senionis
c910dbde79 Add dialog components (#22) 2022-10-01 21:41:47 -04:00
Lukas Senionis
4cbb30c21c Add custom-hook: useQuickAccessVisible (#28) 2022-10-01 21:38:46 -04:00
semantic-release-bot
54a1ef6201 chore(release): 3.3.1 [CI SKIP] 2022-10-02 01:33:33 +00:00
Barend Du Toit
ed0be5e87e fix(SidebarNavigation): add more props (#29) 2022-10-01 21:33:02 -04:00
Travis Lane
a064163b49 updated static-classes (#26) 2022-10-01 21:32:23 -04:00
semantic-release-bot
0b4fcb8d49 chore(release): 3.3.0 [CI SKIP] 2022-10-02 01:31:47 +00:00
Barend Du Toit
4233128c7e feat(Menu): add nested menu groups + more props (#30) 2022-10-01 21:31:18 -04:00
TrainDoctor
43cb2726d8 chore(license): remove un-needed exceptions 2022-09-30 21:50:23 -07:00
TrainDoctor
5d9c506fe7 Update package.json 2022-09-30 21:48:33 -07:00
14 changed files with 210 additions and 52 deletions

View File

@@ -1,3 +1,24 @@
## [3.3.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.1...v3.3.2) (2022-10-02)
### Bug Fixes
* **modal:** allow children ([40871af](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/40871af8539858f435c83123a56d4b31b63d627d))
## [3.3.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.0...v3.3.1) (2022-10-02)
### Bug Fixes
* **SidebarNavigation:** add more props ([#29](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/29)) ([ed0be5e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/ed0be5e87e964ed57cc99b40ff55fe35a2f518b2))
# [3.3.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.2...v3.3.0) (2022-10-02)
### Features
* **Menu:** add nested menu groups + more props ([#30](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/30)) ([4233128](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/4233128c7ee8c6e5ab4ee74385c7b1b911d507a6))
## [3.2.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.1...v3.2.2) (2022-09-29)

20
LICENSE
View File

@@ -1,4 +1,4 @@
GNU LESSER GENERAL PUBLIC LICENSE (DECKY-FRONTEND-LIB v1)
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
@@ -457,24 +457,6 @@ DAMAGES.
END OF TERMS AND CONDITIONS
EXCEPTION NOTICE
1. The exception is that you may use, copy, link, modify and distribute
under your own terms, binary object code versions of works based on the
Library.
2. If you copy code from files distributed under the terms of the GNU
General Public Licence or the GNU Library General Public Licence into a
copy of this library, as this licence permits, the exception does not apply
to the code that you add in this way. To avoid misleading anyone as to the
status of such modified files, you must delete this exception notice from
such code and/or adjust the licensing conditions notice accordingly.
3. If you write modifications of your own for this library, it is your
choice whether to permit this exception to apply to your modifications. If
you do not wish that, you must delete the exception notice from such code
and/or adjust the licensing conditions notice accordingly.
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "decky-frontend-lib",
"version": "3.2.2",
"version": "3.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "decky-frontend-lib",
"version": "3.2.2",
"version": "3.3.2",
"license": "GPL-2.0-or-later",
"dependencies": {
"minimist": "^1.2.6"

View File

@@ -1,6 +1,6 @@
{
"name": "decky-frontend-lib",
"version": "3.2.2",
"version": "3.3.2",
"description": "A library for building decky plugins",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -30,7 +30,7 @@
"components"
],
"author": "Jonas Dellinger <jonas@dellinger.dev>",
"license": "LGPL-2.1-with-decky-exceptions",
"license": "LGPL-2.1",
"bugs": {
"url": "https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues"
},

View File

@@ -0,0 +1 @@
export * from './usequickaccessvisible';

View File

@@ -0,0 +1,67 @@
import { useState, useEffect } from 'react';
declare global {
var FocusNavController: any;
}
/**
* Returns state indicating the visibility of quick access menu.
*
* @remarks
* During development it is possible to open the quick access menu without giving it
* focus in some cases. In such cases, the quick access menu state is invisible.
*
* This seems to be impossible to replicate when running the deck normally. Even in
* the edge cases it always seems to have a focus.
*
* @returns `true` if quick access menu is visible (focused) and `false` otherwise.
*
* @example
* import { VFC, useEffect } from "react";
* import { useQuickAccessVisible } from "decky-frontend-lib";
*
* export const PluginPanelView: VFC<{}> = ({ }) => {
* const isVisible = useQuickAccessVisible();
*
* useEffect(() => {
* if (!isVisible) {
* return;
* }
*
* const interval = setInterval(() => console.log("Hello world!"), 1000);
* return () => {
* clearInterval(interval);
* }
* }, [isVisible])
*
* return (
* <div>
* {isVisible ? "VISIBLE" : "INVISIBLE"}
* </div>
* );
* };
*/
export function useQuickAccessVisible(): boolean {
// Assuming that the component is rendered in QAM already, so true by default...
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const quickAccessWindow: Window | null = FocusNavController?.GetGamepadNavTreeByID("QuickAccess-NA")?.m_Root?.m_element?.ownerDocument.defaultView ?? null;
if (quickAccessWindow === null) {
console.error("Could not get window of QuickAccess menu!");
return;
}
const onBlur = () => setIsVisible(false);
const onFocus = () => setIsVisible(true);
quickAccessWindow.addEventListener("blur", onBlur);
quickAccessWindow.addEventListener("focus", onFocus);
return () => {
quickAccessWindow.removeEventListener("blur", onBlur);
quickAccessWindow.removeEventListener("focus", onFocus);
};
}, []);
return isVisible;
}

View File

@@ -1,22 +1,8 @@
import { CSSProperties, FC, RefAttributes } from 'react';
import { FC } from 'react';
import { DialogButton, DialogButtonProps } from "./Dialog";
import { CommonUIModule } from '../webpack';
import { FooterLegendProps } from './FooterLegend';
export interface DialogButtonProps extends RefAttributes<HTMLDivElement>, FooterLegendProps {
label?: string;
style?: CSSProperties;
className?: string;
noFocusRing?: boolean;
description?: string;
layout?: 'below';
onClick?(e: MouseEvent): void;
disabled?: boolean;
bottomSeparator?: boolean;
export interface ButtonProps extends DialogButtonProps {
}
export const DialogButton = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton'),
) as FC<DialogButtonProps>;
// Button isn't exported, so call DialogButton to grab it
export const Button = (DialogButton as any)?.render({}).type as FC<ButtonProps>;

View File

@@ -0,0 +1,63 @@
import { CommonUIModule } from "../webpack";
import { CSSProperties, FC, RefAttributes } from "react";
import { FooterLegendProps } from './FooterLegend';
export interface DialogCommonProps extends RefAttributes<HTMLDivElement> {
style?: CSSProperties;
className?: string;
}
export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps {
noFocusRing?: boolean;
disabled?: boolean;
onClick?(e: MouseEvent): void;
onPointerDown?(e: PointerEvent): void;
onPointerUp?(e: PointerEvent): void;
onPointerCancel?(e: PointerEvent): void;
onMouseDown?(e: MouseEvent): void;
onMouseUp?(e: MouseEvent): void;
onTouchStart?(e: TouchEvent): void;
onTouchEnd?(e: TouchEvent): void;
onTouchCancel?(e: TouchEvent): void;
onSubmit?(e: SubmitEvent): void;
}
const CommonDialogDivs = Object.values(CommonUIModule).filter((m: any) => typeof m === "object" && m?.render?.toString().includes('"div",Object.assign({},'));
const MappedDialogDivs = new Map(Object.values(CommonDialogDivs).map((m: any) => {
const renderedDiv = m.render({});
// Take only the first class name segment as it identifies the element we want
return [renderedDiv.props.className.split(" ")[0], m]
}));
export const DialogHeader = MappedDialogDivs.get("DialogHeader") as FC<DialogCommonProps>;
export const DialogSubHeader = MappedDialogDivs.get("DialogSubHeader") as FC<DialogCommonProps>;
export const DialogFooter = MappedDialogDivs.get("DialogFooter") as FC<DialogCommonProps>;
export const DialogLabel = MappedDialogDivs.get("DialogLabel") as FC<DialogCommonProps>;
export const DialogBodyText = MappedDialogDivs.get("DialogBodyText") as FC<DialogCommonProps>;
export const DialogBody = MappedDialogDivs.get("DialogBody") as FC<DialogCommonProps>;
export const DialogControlsSection = MappedDialogDivs.get("DialogControlsSection") as FC<DialogCommonProps>;
export const DialogControlsSectionHeader = MappedDialogDivs.get("DialogControlsSectionHeader") as FC<DialogCommonProps>;
export const DialogButtonPrimary = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Primary')
) as FC<DialogButtonProps>;
export const DialogButtonSecondary = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Secondary')
) as FC<DialogButtonProps>;
export const DialogButtonSmall = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Small')
) as FC<DialogButtonProps>;
// This is the "main" button. The Primary can act as a submit button,
// therefore secondary is chosen (also for backwards comp. reasons)
export const DialogButton = DialogButtonSecondary;

View File

@@ -15,6 +15,7 @@ export interface MenuProps {
label: string;
onCancel?(): void;
cancelText?: string;
children?: ReactNode;
}
export const Menu: FC<MenuProps> = findModuleChild((m) => {
@@ -27,8 +28,26 @@ export const Menu: FC<MenuProps> = findModuleChild((m) => {
}
});
export interface MenuGroupProps {
label: string;
disabled?: boolean;
children?: ReactNode;
}
export const MenuGroup: FC<MenuGroupProps> = findModuleChild((m) => {
if (typeof m !== 'object') return undefined;
for (let prop in m) {
if (m[prop]?.prototype?.RenderSubMenu && m[prop]?.prototype?.ShowSubMenu) {
return m[prop];
}
}
});
export interface MenuItemProps {
onSelected?(): void;
disabled?: boolean;
children?: ReactNode;
}
export const MenuItem: FC<MenuItemProps> = findModuleChild((m) => {

View File

@@ -37,6 +37,7 @@ export const showModal: (modal: ReactNode, parent?: EventTarget, props?: ShowMod
});
export interface ModalRootProps {
children: ReactNode;
onCancel?(): void;
closeModal?(): void;
onOK?(): void;

View File

@@ -4,8 +4,14 @@ import { Module, findModuleChild } from '../webpack';
export interface SidebarNavigationPages {
title: string;
route: string;
content: ReactNode;
icon?: ReactNode;
visible?: boolean;
hideTitle?: boolean;
identifier?: string;
route?: string;
link?: string;
padding?: "none" | "compact";
}
export interface SidebarNavigationProps {
@@ -13,6 +19,8 @@ export interface SidebarNavigationProps {
pages: SidebarNavigationPages[];
showTitle?: boolean;
disableRouteReporting?: boolean;
page?: string;
onPageRequested?: (page: string) => void;
}
export const SidebarNavigation = findModuleChild((mod: Module) => {

View File

@@ -1,6 +1,7 @@
export * from './Button';
export * from './ButtonItem';
export * from './Carousel';
export * from './Dialog';
export * from './Dropdown';
export * from './Field';
export * from './Focusable';

View File

@@ -1,6 +1,6 @@
import { findModule } from '../webpack';
type StaticClasses = Record<
type QuickAccessMenuClasses = Record<
| 'ActiveTab'
| 'AllTabContents'
| 'BatteryDetailsLabels'
@@ -63,7 +63,7 @@ type StaticClasses = Record<
string
>;
type ScrollClasses = Record<
type ScrollPanelClasses = Record<
| 'ScrollBoth'
| 'ScrollPanel'
| 'ScrollX'
@@ -355,10 +355,18 @@ type GamepadSliderClasses = Record<
string
>;
export const staticClasses: StaticClasses = findModule((mod) => typeof mod === 'object' && mod.TransitionMenuDelay);
export const scrollClasses: ScrollClasses = findModule((mod) => typeof mod === 'object' && mod.ScrollPanel && mod.ScrollY);
export const gamepadDialogClasses: GamepadDialogClasses = findModule((mod) => typeof mod === 'object' && mod.WithFirstRow);
export const quickAccessControlsClasses: QuickAccessControlsClasses = findModule((mod) => typeof mod === 'object' && mod.PanelSectionRow);
export const updaterFieldClasses: UpdaterFieldClasses = findModule((mod) => typeof mod === 'object' && mod.PatchNotes && mod.PostedTime);
export const playSectionClasses: PlaySectionClasses = findModule((mod) => typeof mod === 'object' && mod.MenuButton && mod.MenuActive);
export const gamepadSliderClasses: GamepadSliderClasses = findModule((mod) => typeof mod === 'object' && mod.SliderTrack && mod.SliderHasNotches);
export const quickAccessMenuClasses: QuickAccessMenuClasses = findModule((mod) => typeof mod === 'object' && mod?.Title?.includes('quickaccessmenu'));
/**
* @depreciated please use quickAccessMenuClasses instead
*/
export const staticClasses = quickAccessMenuClasses;
export const scrollPanelClasses: ScrollPanelClasses = findModule((mod) => typeof mod === 'object' && mod?.ScrollPanel?.includes('scrollpanel'));
/**
* @depreciated please use scrollPanelClasses instead
*/
export const scrollClasses = scrollPanelClasses;
export const gamepadDialogClasses: GamepadDialogClasses = findModule((mod) => typeof mod === 'object' && mod?.GamepadDialogContent?.includes('gamepaddialog'));
export const quickAccessControlsClasses: QuickAccessControlsClasses = findModule((mod) => typeof mod === 'object' && mod?.PanelSection?.includes('quickaccesscontrols'));
export const updaterFieldClasses: UpdaterFieldClasses = findModule((mod) => typeof mod === 'object' && mod?.OOBEUpdateStatusContainer?.includes('updaterfield'));
export const playSectionClasses: PlaySectionClasses = findModule((mod) => typeof mod === 'object' && mod?.Container?.includes('appdetailsplaysection'));
export const gamepadSliderClasses: GamepadSliderClasses = findModule((mod) => typeof mod === 'object' && mod?.SliderControlPanelGroup?.includes('gamepadslider'));

View File

@@ -1,5 +1,6 @@
// export * from './deck-libs';
export * from './custom-components';
export * from './custom-hooks';
export * from './deck-components';
export * from './plugin';
export * from './webpack';