mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 00:37:49 +00:00
basic patch notes viewer, lazy-load settings and store, build frontend as esmodule, add lazy-loaded react-markdown, backend changes to accomodate ESModule frontend
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
const Markdown: FunctionComponent<ReactMarkdownOptions> = (props) => {
|
||||
return <ReactMarkdown remarkPlugins={[remarkGfm]} {...props} />;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Focusable, updaterFieldClasses } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, ReactNode } from 'react';
|
||||
|
||||
interface InlinePatchNotesProps {
|
||||
date: ReactNode;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const InlinePatchNotes: FunctionComponent<InlinePatchNotesProps> = ({ date, title, children, onClick }) => {
|
||||
return (
|
||||
<Focusable className={updaterFieldClasses.PatchNotes} onActivate={onClick}>
|
||||
<div className={updaterFieldClasses.PostedTime}>{date}</div>
|
||||
<div className={updaterFieldClasses.EventDetailTitle}>{title}</div>
|
||||
<div className={updaterFieldClasses.EventDetailsBody}>{children}</div>
|
||||
</Focusable>
|
||||
);
|
||||
};
|
||||
|
||||
export default InlinePatchNotes;
|
||||
@@ -1,9 +1,66 @@
|
||||
import { DialogButton, Field, ProgressBarWithInfo, Spinner } from 'decky-frontend-lib';
|
||||
import { Carousel, DialogButton, Field, Focusable, ProgressBarWithInfo, Spinner, showModal } from 'decky-frontend-lib';
|
||||
import { useCallback } from 'react';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaArrowDown } from 'react-icons/fa';
|
||||
|
||||
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
|
||||
import { useDeckyState } from '../../../DeckyState';
|
||||
import InlinePatchNotes from '../../../patchnotes/InlinePatchNotes';
|
||||
|
||||
const MarkdownRenderer = lazy(() => import('../../../Markdown'));
|
||||
|
||||
// import ReactMarkdown from 'react-markdown'
|
||||
// import remarkGfm from 'remark-gfm'
|
||||
|
||||
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
||||
return (
|
||||
<Focusable onCancelButton={closeModal}>
|
||||
<Carousel
|
||||
fnItemRenderer={(id: number, ...args: any[]) => {
|
||||
console.log(args, versionInfo);
|
||||
return (
|
||||
<Focusable
|
||||
onActivate={() => {}}
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: '40px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>{versionInfo?.all?.[id]?.name}</h1>
|
||||
{versionInfo?.all?.[id]?.body ? (
|
||||
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
|
||||
<MarkdownRenderer>{versionInfo.all[id].body}</MarkdownRenderer>
|
||||
</Suspense>
|
||||
) : (
|
||||
'no patch notes for this version'
|
||||
)}
|
||||
</div>
|
||||
</Focusable>
|
||||
);
|
||||
}}
|
||||
fnGetId={(id) => {
|
||||
return id;
|
||||
}}
|
||||
nNumItems={versionInfo?.all?.length}
|
||||
nHeight={window.innerHeight - 150}
|
||||
nItemHeight={window.innerHeight - 200}
|
||||
nItemMarginX={0}
|
||||
initialColumn={0}
|
||||
autoFocus={true}
|
||||
fnGetColumnWidth={(...args: any[]) => {
|
||||
console.log('cw', args);
|
||||
return window.innerWidth;
|
||||
}}
|
||||
/>
|
||||
</Focusable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UpdaterSettings() {
|
||||
const { isLoaderUpdating, setIsLoaderUpdating } = useDeckyState();
|
||||
@@ -34,56 +91,80 @@ export default function UpdaterSettings() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const showPatchNotes = useCallback(() => {
|
||||
showModal(<PatchNotesModal versionInfo={versionInfo} />);
|
||||
}, [versionInfo]);
|
||||
|
||||
return (
|
||||
<Field
|
||||
label="Updates"
|
||||
description={
|
||||
versionInfo && (
|
||||
<span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${
|
||||
versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : ''
|
||||
}`}</span>
|
||||
)
|
||||
}
|
||||
icon={
|
||||
!versionInfo ? (
|
||||
<Spinner style={{ width: '1em', height: 20, display: 'block' }} />
|
||||
<>
|
||||
<Field
|
||||
onOptionsActionDescription="Patch Notes"
|
||||
onOptionsButton={showPatchNotes}
|
||||
label="Updates"
|
||||
description={
|
||||
versionInfo && (
|
||||
<span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${
|
||||
versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : ''
|
||||
}`}</span>
|
||||
)
|
||||
}
|
||||
icon={
|
||||
!versionInfo ? (
|
||||
<Spinner style={{ width: '1em', height: 20, display: 'block' }} />
|
||||
) : (
|
||||
<FaArrowDown style={{ display: 'block' }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{updateProgress == -1 && !isLoaderUpdating ? (
|
||||
<DialogButton
|
||||
disabled={!versionInfo?.updatable || checkingForUpdates}
|
||||
onClick={
|
||||
!versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? async () => {
|
||||
setCheckingForUpdates(true);
|
||||
const res = (await callUpdaterMethod('check_for_updates')) as { result: VerInfo };
|
||||
setVersionInfo(res.result);
|
||||
setCheckingForUpdates(false);
|
||||
}
|
||||
: async () => {
|
||||
setUpdateProgress(0);
|
||||
callUpdaterMethod('do_update');
|
||||
}
|
||||
}
|
||||
>
|
||||
{checkingForUpdates
|
||||
? 'Checking'
|
||||
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? 'Check For Updates'
|
||||
: 'Install Update'}
|
||||
</DialogButton>
|
||||
) : (
|
||||
<FaArrowDown style={{ display: 'block' }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{updateProgress == -1 && !isLoaderUpdating ? (
|
||||
<DialogButton
|
||||
disabled={!versionInfo?.updatable || checkingForUpdates}
|
||||
onClick={
|
||||
!versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? async () => {
|
||||
setCheckingForUpdates(true);
|
||||
const res = (await callUpdaterMethod('check_for_updates')) as { result: VerInfo };
|
||||
setVersionInfo(res.result);
|
||||
setCheckingForUpdates(false);
|
||||
}
|
||||
: async () => {
|
||||
setUpdateProgress(0);
|
||||
callUpdaterMethod('do_update');
|
||||
}
|
||||
}
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator={false}
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
{versionInfo?.remote && (
|
||||
<InlinePatchNotes
|
||||
title={versionInfo?.remote.name}
|
||||
date={new Intl.RelativeTimeFormat('en-US', {
|
||||
numeric: 'auto',
|
||||
}).format(
|
||||
Math.ceil((new Date(versionInfo.remote.published_at).getTime() - new Date().getTime()) / 86400000),
|
||||
'day',
|
||||
)}
|
||||
onClick={showPatchNotes}
|
||||
>
|
||||
{checkingForUpdates
|
||||
? 'Checking'
|
||||
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? 'Check For Updates'
|
||||
: 'Install Update'}
|
||||
</DialogButton>
|
||||
) : (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator={false}
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
||||
/>
|
||||
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
|
||||
<MarkdownRenderer>{versionInfo?.remote.body}</MarkdownRenderer>
|
||||
</Suspense>
|
||||
</InlinePatchNotes>
|
||||
)}
|
||||
</Field>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ModalRoot, QuickAccessTab, Router, showModal, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { ModalRoot, QuickAccessTab, Router, SteamSpinner, showModal, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { FaPlug } from 'react-icons/fa';
|
||||
|
||||
import { DeckyState, DeckyStateContextProvider, useDeckyState } from './components/DeckyState';
|
||||
@@ -6,8 +7,6 @@ import LegacyPlugin from './components/LegacyPlugin';
|
||||
import PluginInstallModal from './components/modals/PluginInstallModal';
|
||||
import NotificationBadge from './components/NotificationBadge';
|
||||
import PluginView from './components/PluginView';
|
||||
import SettingsPage from './components/settings';
|
||||
import StorePage from './components/store/Store';
|
||||
import TitleView from './components/TitleView';
|
||||
import Logger from './logger';
|
||||
import { Plugin } from './plugin';
|
||||
@@ -61,11 +60,44 @@ class PluginLoader extends Logger {
|
||||
),
|
||||
});
|
||||
|
||||
this.routerHook.addRoute('/decky/store', () => <StorePage />);
|
||||
const StorePage = lazy(() => import('./components/store/Store'));
|
||||
const SettingsPage = lazy(() => import('./components/settings'));
|
||||
|
||||
this.routerHook.addRoute('/decky/store', () => (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<StorePage />
|
||||
</Suspense>
|
||||
));
|
||||
this.routerHook.addRoute('/decky/settings', () => {
|
||||
return (
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<SettingsPage />
|
||||
<Suspense
|
||||
fallback={
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SettingsPage />
|
||||
</Suspense>
|
||||
</DeckyStateContextProvider>
|
||||
);
|
||||
});
|
||||
|
||||
+14
-11
@@ -11,19 +11,22 @@ export interface DeckyUpdater {
|
||||
finish: () => void;
|
||||
}
|
||||
|
||||
export interface RemoteVerInfo {
|
||||
assets: {
|
||||
browser_download_url: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
name: string;
|
||||
body: string;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
export interface VerInfo {
|
||||
current: string;
|
||||
remote: {
|
||||
assets: {
|
||||
browser_download_url: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
name: string;
|
||||
body: string;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
tag_name: string;
|
||||
} | null;
|
||||
remote: RemoteVerInfo | null;
|
||||
all: RemoteVerInfo[] | null;
|
||||
updatable: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user