Skip to content

Commit

Permalink
Merge pull request #1 from beebls/main
Browse files Browse the repository at this point in the history
add `onDocsClick` param for custom Titleviews
  • Loading branch information
PartyWumpus authored Sep 14, 2023
2 parents 2f555a4 + d6ae395 commit 443b9f5
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 177 deletions.
118 changes: 65 additions & 53 deletions frontend/src/components/Docs.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { SidebarNavigation, SteamSpinner, useParams } from 'decky-frontend-lib';
import i18n from 'i18next';
import { VFC, useEffect, useState } from 'react';
import { useParams, SidebarNavigation, SteamSpinner } from "decky-frontend-lib";
import { lazy } from 'react';
import i18n from 'i18next';

import { ScrollArea, Scrollable, scrollableRef } from "./Scrollable";
import { ScrollArea, Scrollable, scrollableRef } from './Scrollable';

const MarkdownRenderer = lazy(() => import('./Markdown'));

const DocsPage: VFC<{ content: string }> = ({ content }) => {
const ref = scrollableRef();
const ref = scrollableRef();

return (
<>
<style>
return (
<>
<style>
{`
.decky-docs-markdown p {white-space: pre-wrap}
.decky-docs-markdown a {text-decoration: none;}
Expand All @@ -23,65 +24,76 @@ const DocsPage: VFC<{ content: string }> = ({ content }) => {
.decky-docs-markdown > .Panel.Focusable.gpfocuswithin {background-color: #868da117;}
.decky-docs-markdown img {max-width: 588px;}
`}
</style>
<Scrollable ref={ref}>
<ScrollArea scrollable={ref} noFocusRing={true}>
<MarkdownRenderer className="decky-docs-markdown" children={content} />
</ScrollArea>
</Scrollable>
</>
)
}
</style>
<Scrollable ref={ref}>
<ScrollArea scrollable={ref} noFocusRing={true}>
<MarkdownRenderer className="decky-docs-markdown" children={content} />
</ScrollArea>
</Scrollable>
</>
);
};

interface DocsPage {
title: string;
text: string;
}

const StorePage: VFC<{}> = () => {
const [docs, setDocs] = useState<(DocsPage | 'separator')[] | null>(null);
const { plugin } = useParams<{ plugin: string }>();

const [docs, setDocs] = useState<(DocsPage | 'separator')[] | null>(null);
const { plugin } = useParams<{ plugin: string }>()

useEffect(() => {
useEffect(() => {
(async () => {
setDocs(await (await fetch(`http://127.0.0.1:1337/docs/${plugin}/${i18n.resolvedLanguage}`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authentication: window.deckyAuthToken,
}
})).json())
})();
}, []);
setDocs(
await (
await fetch(`http://127.0.0.1:1337/docs/${plugin}/${i18n.resolvedLanguage}`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authentication: window.deckyAuthToken,
},
})
).json(),
);
})();
}, []);

return (
<>
{!docs ?
return (
<>
{!docs ? (
<div style={{ height: '100%' }}>
<SteamSpinner />
</div>
: (docs.length == 1) ?
<div style={{padding:"calc(12px + 1.4vw) 2.8vw", paddingTop:"calc( 24px + var(--basicui-header-height, 0px) )", background:"#0e141b"}}>
<DocsPage content={docs[Object.keys(docs)[0]]["text"]} />
</div>
:
<SidebarNavigation
title={plugin}
showTitle={true}
pages={docs.map((file) => (
file == 'separator' ? 'separator' : {
title: file["title"],
content:<DocsPage content={file["text"]} />,
route: `/decky/docs/${plugin}/${file["title"]}`,
hideTitle: true,
}
))}
/>
}
) : docs.length == 1 ? (
<div
style={{
padding: 'calc(12px + 1.4vw) 2.8vw',
paddingTop: 'calc( 24px + var(--basicui-header-height, 0px) )',
background: '#0e141b',
}}
>
<DocsPage content={docs[Object.keys(docs)[0]]['text']} />
</div>
) : (
<SidebarNavigation
title={plugin}
showTitle={true}
pages={docs.map((file) =>
file == 'separator'
? 'separator'
: {
title: file['title'],
content: <DocsPage content={file['text']} />,
route: `/decky/docs/${plugin}/${file['title']}`,
hideTitle: true,
},
)}
/>
)}
</>
)
}
);
};

export default StorePage;
188 changes: 87 additions & 101 deletions frontend/src/components/Scrollable.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,38 @@
import { Focusable, FocusableProps, GamepadButton, GamepadEvent, ServerAPI } from 'decky-frontend-lib';
// this file was mostly (the good bits) made by davocarli#7308
// it should probably be moved to DFL eventually
import { FC, ForwardRefExoticComponent } from "react"
import {
Focusable,
FocusableProps,
GamepadEvent,
GamepadButton,
ServerAPI,
} from "decky-frontend-lib"
import React, { useRef } from "react"
import { FC, ForwardRefExoticComponent } from 'react';
import React, { useRef } from 'react';

const DEFAULTSCROLLSPEED = 50
const DEFAULTSCROLLSPEED = 50;

export interface ScrollableElement extends HTMLDivElement {}

export function scrollableRef() {
return useRef<ScrollableElement>(null)
return useRef<ScrollableElement>(null);
}

export const Scrollable: ForwardRefExoticComponent<any> = React.forwardRef(
(props, ref) => {
if (!props.style) {
props.style = {}
}
// props.style.minHeight = '100%';
// props.style.maxHeight = '80%';
props.style.height = "85vh" // was 95vh previously!
props.style.overflowY = "scroll"
export const Scrollable: ForwardRefExoticComponent<any> = React.forwardRef((props, ref) => {
if (!props.style) {
props.style = {};
}
// props.style.minHeight = '100%';
// props.style.maxHeight = '80%';
props.style.height = '85vh'; // was 95vh previously!
props.style.overflowY = 'scroll';

return (
<React.Fragment>
<div ref={ref} {...props} />
</React.Fragment>
)
}
)
return (
<React.Fragment>
<div ref={ref} {...props} />
</React.Fragment>
);
});

interface ScrollAreaProps extends FocusableProps {
scrollable: React.RefObject<ScrollableElement>
scrollSpeed?: number
serverApi?: ServerAPI
noFocusRing?: boolean
scrollable: React.RefObject<ScrollableElement>;
scrollSpeed?: number;
serverApi?: ServerAPI;
noFocusRing?: boolean;
}

// const writeLog = async (serverApi: ServerAPI, content: any) => {
Expand All @@ -49,81 +41,75 @@ interface ScrollAreaProps extends FocusableProps {
// }

const scrollOnDirection = (
e: GamepadEvent,
ref: React.RefObject<ScrollableElement>,
amt: number,
prev: React.RefObject<HTMLDivElement>,
next: React.RefObject<HTMLDivElement>
e: GamepadEvent,
ref: React.RefObject<ScrollableElement>,
amt: number,
prev: React.RefObject<HTMLDivElement>,
next: React.RefObject<HTMLDivElement>,
) => {
let childNodes = ref.current?.childNodes
let currentIndex = null
childNodes?.forEach((node, i) => {
if (node == e.currentTarget) {
currentIndex = i
}
})
let childNodes = ref.current?.childNodes;
let currentIndex = null;
childNodes?.forEach((node, i) => {
if (node == e.currentTarget) {
currentIndex = i;
}
});

// @ts-ignore
let pos = e.currentTarget?.getBoundingClientRect()
let out = ref.current?.getBoundingClientRect()
// @ts-ignore
let pos = e.currentTarget?.getBoundingClientRect();
let out = ref.current?.getBoundingClientRect();

if (e.detail.button == GamepadButton.DIR_DOWN) {
if (
out?.bottom != undefined &&
pos.bottom <= out.bottom &&
currentIndex != null &&
childNodes != undefined &&
currentIndex + 1 < childNodes.length
) {
next.current?.focus()
} else {
ref.current?.scrollBy({ top: amt, behavior: "auto" })
}
} else if (e.detail.button == GamepadButton.DIR_UP) {
if (
out?.top != undefined &&
pos.top >= out.top &&
currentIndex != null &&
childNodes != undefined &&
currentIndex - 1 >= 0
) {
prev.current?.focus()
} else {
ref.current?.scrollBy({ top: -amt, behavior: "auto" })
}
} else if (e.detail.button == GamepadButton.DIR_LEFT) {
throw("this is not a real error, just a (temporary?) workaround to make navigation work in docs")
}
}
if (e.detail.button == GamepadButton.DIR_DOWN) {
if (
out?.bottom != undefined &&
pos.bottom <= out.bottom &&
currentIndex != null &&
childNodes != undefined &&
currentIndex + 1 < childNodes.length
) {
next.current?.focus();
} else {
ref.current?.scrollBy({ top: amt, behavior: 'auto' });
}
} else if (e.detail.button == GamepadButton.DIR_UP) {
if (
out?.top != undefined &&
pos.top >= out.top &&
currentIndex != null &&
childNodes != undefined &&
currentIndex - 1 >= 0
) {
prev.current?.focus();
} else {
ref.current?.scrollBy({ top: -amt, behavior: 'auto' });
}
} else if (e.detail.button == GamepadButton.DIR_LEFT) {
throw 'this is not a real error, just a (temporary?) workaround to make navigation work in docs';
}
};

export const ScrollArea: FC<ScrollAreaProps> = (props) => {
let scrollSpeed = DEFAULTSCROLLSPEED
if (props.scrollSpeed) {
scrollSpeed = props.scrollSpeed
}
let scrollSpeed = DEFAULTSCROLLSPEED;
if (props.scrollSpeed) {
scrollSpeed = props.scrollSpeed;
}

const prevFocus = useRef<HTMLDivElement>(null)
const nextFocus = useRef<HTMLDivElement>(null)
const prevFocus = useRef<HTMLDivElement>(null);
const nextFocus = useRef<HTMLDivElement>(null);

props.onActivate = (e) => {
const ele = e.currentTarget as HTMLElement
ele.focus()
}
props.onGamepadDirection = (e) => {
scrollOnDirection(
e,
props.scrollable,
scrollSpeed,
prevFocus,
nextFocus
)
}
props.onActivate = (e) => {
const ele = e.currentTarget as HTMLElement;
ele.focus();
};
props.onGamepadDirection = (e) => {
scrollOnDirection(e, props.scrollable, scrollSpeed, prevFocus, nextFocus);
};

return (
<React.Fragment>
<Focusable noFocusRing={props.noFocusRing ?? false} ref={prevFocus} children={[]} onActivate={() => {}} />
<Focusable {...props} />
<Focusable noFocusRing={props.noFocusRing ?? false} ref={nextFocus} children={[]} onActivate={() => {}} />
</React.Fragment>
)
}
return (
<React.Fragment>
<Focusable noFocusRing={props.noFocusRing ?? false} ref={prevFocus} children={[]} onActivate={() => {}} />
<Focusable {...props} />
<Focusable noFocusRing={props.noFocusRing ?? false} ref={nextFocus} children={[]} onActivate={() => {}} />
</React.Fragment>
);
};
Loading

0 comments on commit 443b9f5

Please sign in to comment.