mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-20 18:10:08 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5dc08a977 | ||
|
|
40871af853 | ||
|
|
c910dbde79 | ||
|
|
4cbb30c21c | ||
|
|
54a1ef6201 | ||
|
|
ed0be5e87e | ||
|
|
a064163b49 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
## [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)
|
# [3.3.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.2...v3.3.0) (2022-10-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "decky-frontend-lib",
|
"name": "decky-frontend-lib",
|
||||||
"version": "3.3.0",
|
"version": "3.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "decky-frontend-lib",
|
"name": "decky-frontend-lib",
|
||||||
"version": "3.3.0",
|
"version": "3.3.2",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "decky-frontend-lib",
|
"name": "decky-frontend-lib",
|
||||||
"version": "3.3.0",
|
"version": "3.3.2",
|
||||||
"description": "A library for building decky plugins",
|
"description": "A library for building decky plugins",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
1
src/custom-hooks/index.ts
Normal file
1
src/custom-hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './usequickaccessvisible';
|
||||||
67
src/custom-hooks/usequickaccessvisible.tsx
Normal file
67
src/custom-hooks/usequickaccessvisible.tsx
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,22 +1,8 @@
|
|||||||
import { CSSProperties, FC, RefAttributes } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { DialogButton, DialogButtonProps } from "./Dialog";
|
||||||
|
|
||||||
import { CommonUIModule } from '../webpack';
|
export interface ButtonProps extends DialogButtonProps {
|
||||||
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 const DialogButton = Object.values(CommonUIModule).find(
|
// Button isn't exported, so call DialogButton to grab it
|
||||||
(mod: any) =>
|
export const Button = (DialogButton as any)?.render({}).type as FC<ButtonProps>;
|
||||||
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
|
|
||||||
mod?.render?.toString()?.includes('DialogButton'),
|
|
||||||
) as FC<DialogButtonProps>;
|
|
||||||
|
|||||||
63
src/deck-components/Dialog.tsx
Normal file
63
src/deck-components/Dialog.tsx
Normal 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;
|
||||||
@@ -37,6 +37,7 @@ export const showModal: (modal: ReactNode, parent?: EventTarget, props?: ShowMod
|
|||||||
});
|
});
|
||||||
|
|
||||||
export interface ModalRootProps {
|
export interface ModalRootProps {
|
||||||
|
children: ReactNode;
|
||||||
onCancel?(): void;
|
onCancel?(): void;
|
||||||
closeModal?(): void;
|
closeModal?(): void;
|
||||||
onOK?(): void;
|
onOK?(): void;
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ import { Module, findModuleChild } from '../webpack';
|
|||||||
|
|
||||||
export interface SidebarNavigationPages {
|
export interface SidebarNavigationPages {
|
||||||
title: string;
|
title: string;
|
||||||
route: string;
|
|
||||||
content: ReactNode;
|
content: ReactNode;
|
||||||
|
icon?: ReactNode;
|
||||||
|
visible?: boolean;
|
||||||
|
hideTitle?: boolean;
|
||||||
|
identifier?: string;
|
||||||
|
route?: string;
|
||||||
|
link?: string;
|
||||||
|
padding?: "none" | "compact";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarNavigationProps {
|
export interface SidebarNavigationProps {
|
||||||
@@ -13,6 +19,8 @@ export interface SidebarNavigationProps {
|
|||||||
pages: SidebarNavigationPages[];
|
pages: SidebarNavigationPages[];
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
disableRouteReporting?: boolean;
|
disableRouteReporting?: boolean;
|
||||||
|
page?: string;
|
||||||
|
onPageRequested?: (page: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarNavigation = findModuleChild((mod: Module) => {
|
export const SidebarNavigation = findModuleChild((mod: Module) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './ButtonItem';
|
export * from './ButtonItem';
|
||||||
export * from './Carousel';
|
export * from './Carousel';
|
||||||
|
export * from './Dialog';
|
||||||
export * from './Dropdown';
|
export * from './Dropdown';
|
||||||
export * from './Field';
|
export * from './Field';
|
||||||
export * from './Focusable';
|
export * from './Focusable';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { findModule } from '../webpack';
|
import { findModule } from '../webpack';
|
||||||
|
|
||||||
type StaticClasses = Record<
|
type QuickAccessMenuClasses = Record<
|
||||||
| 'ActiveTab'
|
| 'ActiveTab'
|
||||||
| 'AllTabContents'
|
| 'AllTabContents'
|
||||||
| 'BatteryDetailsLabels'
|
| 'BatteryDetailsLabels'
|
||||||
@@ -63,7 +63,7 @@ type StaticClasses = Record<
|
|||||||
string
|
string
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type ScrollClasses = Record<
|
type ScrollPanelClasses = Record<
|
||||||
| 'ScrollBoth'
|
| 'ScrollBoth'
|
||||||
| 'ScrollPanel'
|
| 'ScrollPanel'
|
||||||
| 'ScrollX'
|
| 'ScrollX'
|
||||||
@@ -355,10 +355,18 @@ type GamepadSliderClasses = Record<
|
|||||||
string
|
string
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const staticClasses: StaticClasses = findModule((mod) => typeof mod === 'object' && mod.TransitionMenuDelay);
|
export const quickAccessMenuClasses: QuickAccessMenuClasses = findModule((mod) => typeof mod === 'object' && mod?.Title?.includes('quickaccessmenu'));
|
||||||
export const scrollClasses: ScrollClasses = findModule((mod) => typeof mod === 'object' && mod.ScrollPanel && mod.ScrollY);
|
/**
|
||||||
export const gamepadDialogClasses: GamepadDialogClasses = findModule((mod) => typeof mod === 'object' && mod.WithFirstRow);
|
* @depreciated please use quickAccessMenuClasses instead
|
||||||
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 staticClasses = quickAccessMenuClasses;
|
||||||
export const playSectionClasses: PlaySectionClasses = findModule((mod) => typeof mod === 'object' && mod.MenuButton && mod.MenuActive);
|
export const scrollPanelClasses: ScrollPanelClasses = findModule((mod) => typeof mod === 'object' && mod?.ScrollPanel?.includes('scrollpanel'));
|
||||||
export const gamepadSliderClasses: GamepadSliderClasses = findModule((mod) => typeof mod === 'object' && mod.SliderTrack && mod.SliderHasNotches);
|
/**
|
||||||
|
* @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'));
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// export * from './deck-libs';
|
// export * from './deck-libs';
|
||||||
export * from './custom-components';
|
export * from './custom-components';
|
||||||
|
export * from './custom-hooks';
|
||||||
export * from './deck-components';
|
export * from './deck-components';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
export * from './webpack';
|
export * from './webpack';
|
||||||
|
|||||||
Reference in New Issue
Block a user