Skip to content

[UX] Help component #3135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@
"@typescript-eslint/return-await": "warn",
"react/no-unknown-property": [
"error",
{ "ignore": ["partition", "allowpopups", "useragent"] }
{
"ignore": [
"partition",
"allowpopups",
"useragent",
"popover",
"popovertarget"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typescript doesn't understand these properties, they are too new

]
}
],
"@typescript-eslint/promise-function-async": [
"error",
Expand Down
8 changes: 7 additions & 1 deletion public/locales/en/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@
"noDescription": "No description available"
},
"help": {
"cloud_save_unsupported": "This game does not support cloud saves. This information is provided by the game developers. Some games do implement their own cloud save system"
"cloud_save_unsupported": "This game does not support cloud saves. This information is provided by the game developers. Some games do implement their own cloud save system",
"content": {
"gamePage": "Show all game details and actions. Use the 3 dots menu for more options."
},
"title": {
"gamePage": "Game Page"
}
},
"hours": "Hours",
"how-long-to-beat": {
Expand Down
24 changes: 24 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,21 @@
},
"help": {
"amdfsr": "AMD's FSR helps boost framerate by upscaling lower resolutions in Fullscreen Mode. Image quality increases from 5 to 1 at the cost of a slight performance hit. Enabling may improve performance.",
"button": {
"close": "Close Help",
"open": "Open Help"
},
"content": {
"accessibility": "Shows accessibility settings.",
"customThemesPath": "Check our wiki.",
"defaultInstallPath": "This is the default path preselected when installing games.",
"downloadManager": "Shows current and past downloads.",
"library": "Shows all owned games.",
"login": "Log in into the different stores.",
"settingsDefault": "Shows all settings of Heroic and defaults for games.",
"settingsGame": "Show all settings for a game.",
"wineManager": "Install different versions of Wine, Proton, Crossover, etc."
},
"custom_themes_path": "Do not use CSS files from untrusted sources. When in doubt, ask for a review in our Discord channel.",
"custom_themes_wiki": "Check the Wiki for more details on adding custom themes. Click here.",
"disable_logs": "Toggle this checkbox ON to disable most of the writes to log files (critical information is always logged). Make sure to turn OFF this setting before reporting any issue.",
Expand Down Expand Up @@ -311,6 +326,14 @@
"part3": "Manual Sync: Choose Download to download the games saves stored on the Cloud. Upload to upload the local ones to the cloud. Force Download and Force Upload will ignore the version that is locally or on the cloud.",
"part4": "Sync Saves Automatically will sync the saves every time you Start a Game and after finishing playing."
},
"title": {
"accessibility": "Accessibility",
"downloadManager": "Download Manager",
"library": "Library",
"login": "Login",
"settings": "Settings",
"wineManager": "Wine Manager"
},
"vkd3d": "VKD3D is a Vulkan-based translational layer for DirectX 12 games. Enabling may improve compatibility significantly. Has no effect on older DirectX games, it requires DXVK.",
"wine": {
"part1": "Heroic searches for versions of Wine, Proton, and CrossOver in the following folders:",
Expand Down Expand Up @@ -570,6 +593,7 @@
"esync": "Enable Esync",
"exit-to-tray": "Exit to System Tray",
"experimental_features": {
"enableHelp": "Help component",
"enableNewDesign": "New design"
},
"frameless-window": {
Expand Down
1 change: 1 addition & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type Release = {

export type ExperimentalFeatures = {
enableNewDesign: boolean
enableHelp: boolean
}

export interface AppSettings extends GameSettings {
Expand Down
15 changes: 15 additions & 0 deletions src/frontend/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ body {
grid-area: offline;
}

.HelpButton {
position: fixed;
bottom: 0;
right: 0;
}

#root:has(dialog:popover-open) {
pointer-events: none;

.HelpButton,
:popover-open {
pointer-events: all;
}
}

.danger {
font-weight: var(--bold);
color: var(--text-danger, var(--danger));
Expand Down
6 changes: 4 additions & 2 deletions src/frontend/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Sidebar from './components/UI/Sidebar'
import Settings from './screens/Settings'
import Accessibility from './screens/Accessibility'
import ContextProvider from './state/ContextProvider'
import { ControllerHints, OfflineMessage } from './components/UI'
import { ControllerHints, Help, OfflineMessage } from './components/UI'
import DownloadManager from './screens/DownloadManager'
import DialogHandler from './components/UI/DialogHandler'
import SettingsModal from './screens/Settings/components/SettingsModal'
Expand All @@ -25,7 +25,8 @@ function App() {
isRTL,
isFullscreen,
isFrameless,
experimentalFeatures
experimentalFeatures,
help
} = useContext(ContextProvider)

const hasNativeOverlayControls = navigator['windowControlsOverlay']?.visible
Expand Down Expand Up @@ -88,6 +89,7 @@ function App() {
<div className="simple-keyboard"></div>
</div>
{showOverlayControls && <WindowControls />}
{experimentalFeatures.enableHelp && <Help items={help.items} />}
</HashRouter>
</div>
)
Expand Down
8 changes: 6 additions & 2 deletions src/frontend/components/UI/Dialog/components/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ export const Dialog: React.FC<DialogProps> = ({
onCloseRef.current()
}
dialog.addEventListener('cancel', cancel)
dialog['showModal']()
dialog['showPopover']()

return () => {
dialog.removeEventListener('cancel', cancel)
dialog['close']()
dialog['hidePopover']()
}
}
return
Expand Down Expand Up @@ -65,6 +66,9 @@ export const Dialog: React.FC<DialogProps> = ({
className={`Dialog__element ${className}`}
ref={dialogRef}
onClick={onDialogClick}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore, this feature is new and not yet typed
popover="manual"
>
{showCloseButton && (
<div className="Dialog__Close">
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/components/UI/Dialog/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
transform 500ms;
max-width: min(700px, 85vw);
}
.Dialog__element::backdrop {
background: rgba(0, 0, 0, 0.4);
}

.Dialog__element[open] {
.Dialog__element:popover-open {
opacity: 1;
transform: translateY(0);
box-shadow: 0px 0px 0px 100vmax var(--modal-backdrop);
Expand Down
73 changes: 73 additions & 0 deletions src/frontend/components/UI/Help/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.HelpButton,
.HelpContent {
transition: transform 300ms ease-out;
z-index: 100;
}

.HelpButton {
transform: translateX(0);
color: white;
border: 1px solid white;
border-radius: 0px;
background: black;
padding: 0.5rem 1rem;
font-size: 2rem;
font-weight: bold;
opacity: 0.5;
transition:
opacity 100ms linear,
transform 300ms ease-out;
cursor: pointer;

&:hover,
&:focus {
opacity: 1;
}

&.open {
transform: translateX(-30vw);
}
}

.HelpContent {
display: block;
background: var(--background);
color: var(--text-default);
border: 1px solid var(--text-default);
top: 0px;
height: 100vh;
width: 30vw;
right: 0;
left: 70vw;
padding: 0 1rem;
transform: translateX(100%);
transition: transform 300ms ease-out;
text-align: start;

&:popover-open {
transform: translateX(0);
}

& button {
background: white;
border: 0;
border-radius: 0 0 0 50%;
padding: 0.5rem 1rem;
}

& details:not(:last-child) {
border-bottom: 1px solid var(--text-default);
}

& summary {
font-size: 1.2rem;
}

& p {
font-size: 1.1rem;
}

& summary + p {
margin-top: 0;
}
}
51 changes: 51 additions & 0 deletions src/frontend/components/UI/Help/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState } from 'react'

import './index.css'
import { HelpItem } from 'frontend/types'
import { useTranslation } from 'react-i18next'

interface Props {
items: { [key: string]: HelpItem }
}

export default function Help({ items }: Props) {
const { t } = useTranslation()
const [open, setOpen] = useState(false)

const toggleOpen = () => {
setOpen(!open)
}

return (
<>
<button
className={`HelpButton ${open ? 'open' : ''}`}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore, this feature is new and not yet typed
popovertarget="help_content"
onClick={() => toggleOpen()}
title={
open
? t('help.button.close', 'Close Help')
: t('help.button.open', 'Open Help')
}
>
?
</button>
<div
className={`HelpContent ${open ? 'open' : ''}`}
id="help_content"
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore, this feature is new and not yet typed
popover="manual"
>
{Object.keys(items).map((key) => (
<details key={key}>
<summary>{items[key]['title']}</summary>
{items[key]['content']}
</details>
))}
</div>
</>
)
}
7 changes: 7 additions & 0 deletions src/frontend/components/UI/ThemeSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ContextProvider from 'frontend/state/ContextProvider'
import { SelectField, InfoBox, PathSelectionBox } from '..'
import { AppSettings } from 'common/types'
import { writeConfig } from 'frontend/helpers'
import { hasHelp } from 'frontend/hooks/hasHelp'

export const defaultThemes = {
midnightMirage: 'Midnight Mirage',
Expand All @@ -29,6 +30,12 @@ export const ThemeSelector = () => {
const [themesPath, setThemesPath] = useState('')
const [themes, setThemes] = useState<string[]>(Object.keys(defaultThemes))

hasHelp(
'customThemesPath',
t('setting.custom_themes_path', 'Custom Themes Path'),
<p>{t('help.content.customThemesPath', 'Check our wiki.')}</p>
)

// load themes from the custom themes path
const loadThemes = async () => {
const themes = await window.api.getCustomThemes()
Expand Down
1 change: 1 addition & 0 deletions src/frontend/components/UI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as CachedImage } from './CachedImage'
export { default as OfflineMessage } from './OfflineMessage'
export { default as PathSelectionBox } from './PathSelectionBox'
export { default as Winetricks } from './Winetricks'
export { default as Help } from './Help'
21 changes: 21 additions & 0 deletions src/frontend/hooks/hasHelp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ContextProvider from 'frontend/state/ContextProvider'
import { useEffect, useContext } from 'react'

export const hasHelp = (
helpItemId: string,
title: string,
content: JSX.Element
) => {
const { help } = useContext(ContextProvider)

useEffect(() => {
help.addHelpItem(helpItemId, {
title,
content
})

return () => {
help.removeHelpItem(helpItemId)
}
}, [])
}
7 changes: 7 additions & 0 deletions src/frontend/screens/Accessibility/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ToggleSwitch from 'frontend/components/UI/ToggleSwitch'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSyncAlt } from '@fortawesome/free-solid-svg-icons'
import './index.css'
import { hasHelp } from 'frontend/hooks/hasHelp'

export default React.memo(function Accessibility() {
const { t } = useTranslation()
Expand All @@ -28,6 +29,12 @@ export default React.memo(function Accessibility() {
setSecondaryFontFamily
} = useContext(ContextProvider)

hasHelp(
'accessibility',
t('help.title.accessibility', 'Accessibility'),
<p>{t('help.content.accessibility', 'Shows accessibility settings.')}</p>
)

const [fonts, setFonts] = useState<string[]>([])
const [refreshing, setRefreshing] = useState(false)
const [contentFont, setContentFont] = useState('')
Expand Down
9 changes: 9 additions & 0 deletions src/frontend/screens/DownloadManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DownloadManagerHeader from './DownloadManagerHeader'
import { downloadManagerStore } from 'frontend/helpers/electronStores'
import { DMQueue } from 'frontend/types'
import DownloadManagerItem from './components/DownloadManagerItem'
import { hasHelp } from 'frontend/hooks/hasHelp'

export default React.memo(function DownloadManager(): JSX.Element | null {
const { t } = useTranslation()
Expand All @@ -18,6 +19,14 @@ export default React.memo(function DownloadManager(): JSX.Element | null {
const [currentElement, setCurrentElement] = useState<DMQueueElement>()
const [finishedElem, setFinishedElem] = useState<DMQueueElement[]>()

hasHelp(
'downloadManager',
t('help.title.downloadManager', 'Download Manager'),
<p>
{t('help.content.downloadManager', 'Shows current and past downloads.')}
</p>
)

useEffect(() => {
setRefreshing(true)
window.api.getDMQueueInformation().then(({ elements, state }: DMQueue) => {
Expand Down
Loading