mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-23 03:18:48 +02:00
120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
|
|
import { FC, ReactNode } from 'react';
|
||
|
|
|
||
|
|
import { findSP } from '../utils';
|
||
|
|
import { Export, findModule, findModuleByExport, findModuleExport } from '../webpack';
|
||
|
|
|
||
|
|
// All of the popout options + strTitle are related. Proper usage is not yet known...
|
||
|
|
export interface ShowModalProps {
|
||
|
|
browserContext?: unknown;
|
||
|
|
bForcePopOut?: boolean;
|
||
|
|
bHideActionIcons?: boolean;
|
||
|
|
bHideMainWindowForPopouts?: boolean;
|
||
|
|
bNeverPopOut?: boolean;
|
||
|
|
fnOnClose?: () => void; // Seems to be the same as "closeModal" callback, but only when the modal is a popout. Will no longer work after "Update" invocation!
|
||
|
|
popupHeight?: number;
|
||
|
|
popupWidth?: number;
|
||
|
|
promiseRenderComplete?: Promise<void>; // Invoked once the render is complete. Currently, it seems to be used as image loading success/error callback...
|
||
|
|
strTitle?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ShowModalResult {
|
||
|
|
// This method will not invoke any of the variations of "closeModal" callbacks!
|
||
|
|
Close: () => void;
|
||
|
|
|
||
|
|
// This method will replace the modal element completely and will not update the callback chains,
|
||
|
|
// meaning that "closeModal" and etc. will not automatically close the modal anymore (also "fnOnClose"
|
||
|
|
// will not be even called upon close anymore)! You have to manually call the "Close" method when, for example,
|
||
|
|
// the "closeModal" is invoked in the newly updated modal:
|
||
|
|
// <ModalRoot closeModal={() => { console.log("ABOUT TO CLOSE"); showModalRes.Close(); }} />
|
||
|
|
Update: (modal: ReactNode) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const showModalRaw: (
|
||
|
|
modal: ReactNode,
|
||
|
|
parent?: EventTarget,
|
||
|
|
title?: string,
|
||
|
|
props?: ShowModalProps,
|
||
|
|
unknown1?: unknown,
|
||
|
|
hideActions?: { bHideActions?: boolean },
|
||
|
|
modalManager?: unknown,
|
||
|
|
) => ShowModalResult = findModuleExport(
|
||
|
|
(e: Export) =>
|
||
|
|
typeof e === 'function' && e.toString().includes('props.bDisableBackgroundDismiss') && !e?.prototype?.Cancel,
|
||
|
|
);
|
||
|
|
|
||
|
|
export const showModal = (
|
||
|
|
modal: ReactNode,
|
||
|
|
parent?: EventTarget,
|
||
|
|
props: ShowModalProps = {
|
||
|
|
strTitle: 'Decky Dialog',
|
||
|
|
bHideMainWindowForPopouts: false,
|
||
|
|
},
|
||
|
|
): ShowModalResult => {
|
||
|
|
return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, {
|
||
|
|
bHideActions: props.bHideActionIcons,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
export interface ModalRootProps {
|
||
|
|
children?: ReactNode;
|
||
|
|
onCancel?(): void;
|
||
|
|
closeModal?(): void;
|
||
|
|
onOK?(): void;
|
||
|
|
onEscKeypress?(): void;
|
||
|
|
className?: string;
|
||
|
|
modalClassName?: string;
|
||
|
|
bAllowFullSize?: boolean;
|
||
|
|
bDestructiveWarning?: boolean;
|
||
|
|
bDisableBackgroundDismiss?: boolean;
|
||
|
|
bHideCloseIcon?: boolean;
|
||
|
|
bOKDisabled?: boolean;
|
||
|
|
bCancelDisabled?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ConfirmModalProps extends ModalRootProps {
|
||
|
|
onMiddleButton?(): void; // setting this prop will enable the middle button
|
||
|
|
strTitle?: ReactNode;
|
||
|
|
strDescription?: ReactNode;
|
||
|
|
strOKButtonText?: ReactNode;
|
||
|
|
strCancelButtonText?: ReactNode;
|
||
|
|
strMiddleButtonText?: ReactNode;
|
||
|
|
bAlertDialog?: boolean; // This will open a modal with only OK button enabled
|
||
|
|
bMiddleDisabled?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const ConfirmModal = findModuleExport(
|
||
|
|
(e: Export) => !e?.prototype?.OK && e?.prototype?.Cancel && e?.prototype?.render,
|
||
|
|
) as FC<ConfirmModalProps>;
|
||
|
|
|
||
|
|
export const ModalRoot = Object.values(
|
||
|
|
findModule((m: any) => {
|
||
|
|
if (typeof m !== 'object') return false;
|
||
|
|
|
||
|
|
for (let prop in m) {
|
||
|
|
if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}) || {},
|
||
|
|
)?.find((x: any) => x?.type?.toString()?.includes('((function(){')) as FC<ModalRootProps>;
|
||
|
|
|
||
|
|
interface SimpleModalProps {
|
||
|
|
active?: boolean;
|
||
|
|
children: ReactNode;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ModalModule = findModuleByExport((e: Export) => e?.toString().includes('.ModalPosition,fallback:'), 5);
|
||
|
|
|
||
|
|
const ModalModuleProps = ModalModule ? Object.values(ModalModule) : [];
|
||
|
|
|
||
|
|
export const SimpleModal = ModalModuleProps.find((prop) => {
|
||
|
|
const string = prop?.toString();
|
||
|
|
return string?.includes('.ShowPortalModal()') && string?.includes('.OnElementReadyCallbacks.Register(');
|
||
|
|
}) as FC<SimpleModalProps>;
|
||
|
|
|
||
|
|
export const ModalPosition = ModalModuleProps.find((prop) =>
|
||
|
|
prop?.toString().includes('.ModalPosition,fallback:'),
|
||
|
|
) as FC<SimpleModalProps>;
|