Add tab to Settings dialog to view keyboard shortcuts (#2567)
* Add keyboard custom icon * Refactor Settings to be more modular * Add basic keybindings view to settings * Add more shortcuts * Add link to see keyboard shortcuts tab * Little more bottom padding * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Add keybindings to settings search * Add a playwright test for opening the the keyboard shortcuts * fmt --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -3,11 +3,23 @@ import { CustomIcon } from 'components/CustomIcon'
|
||||
import decamelize from 'decamelize'
|
||||
import Fuse from 'fuse.js'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { interactionMap } from 'lib/settings/initialKeybindings'
|
||||
import { Setting } from 'lib/settings/initialSettings'
|
||||
import { SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
type ExtendedSettingsLevel = SettingsLevel | 'keybindings'
|
||||
|
||||
export type SettingsSearchItem = {
|
||||
name: string
|
||||
displayName: string
|
||||
description: string
|
||||
category: string
|
||||
level: ExtendedSettingsLevel
|
||||
}
|
||||
|
||||
export function SettingsSearchBar() {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
useHotkeys(
|
||||
@ -21,29 +33,40 @@ export function SettingsSearchBar() {
|
||||
const navigate = useNavigate()
|
||||
const [query, setQuery] = useState('')
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const settingsAsSearchable = useMemo(
|
||||
() =>
|
||||
Object.entries(settings.state.context).flatMap(
|
||||
const settingsAsSearchable: SettingsSearchItem[] = useMemo(
|
||||
() => [
|
||||
...Object.entries(settings.state.context).flatMap(
|
||||
([category, categorySettings]) =>
|
||||
Object.entries(categorySettings).flatMap(([settingName, setting]) => {
|
||||
const s = setting as Setting
|
||||
return ['project', 'user']
|
||||
return (['project', 'user'] satisfies SettingsLevel[])
|
||||
.filter((l) => s.hideOnLevel !== l)
|
||||
.map((l) => ({
|
||||
category: decamelize(category, { separator: ' ' }),
|
||||
settingName: settingName,
|
||||
settingNameDisplay: decamelize(settingName, { separator: ' ' }),
|
||||
setting: s,
|
||||
level: l,
|
||||
name: settingName,
|
||||
description: s.description ?? '',
|
||||
displayName: decamelize(settingName, { separator: ' ' }),
|
||||
level: l as ExtendedSettingsLevel,
|
||||
}))
|
||||
})
|
||||
),
|
||||
...Object.entries(interactionMap).flatMap(
|
||||
([category, categoryKeybindings]) =>
|
||||
categoryKeybindings.map((keybinding) => ({
|
||||
name: keybinding.name,
|
||||
displayName: keybinding.title,
|
||||
description: keybinding.description,
|
||||
category: category,
|
||||
level: 'keybindings' as ExtendedSettingsLevel,
|
||||
}))
|
||||
),
|
||||
],
|
||||
[settings.state.context]
|
||||
)
|
||||
const [searchResults, setSearchResults] = useState(settingsAsSearchable)
|
||||
|
||||
const fuse = new Fuse(settingsAsSearchable, {
|
||||
keys: ['category', 'settingNameDisplay', 'setting.description'],
|
||||
keys: ['category', 'displayName', 'description'],
|
||||
includeScore: true,
|
||||
})
|
||||
|
||||
@ -52,16 +75,8 @@ export function SettingsSearchBar() {
|
||||
setSearchResults(query.length > 0 ? results : settingsAsSearchable)
|
||||
}, [query])
|
||||
|
||||
function handleSelection({
|
||||
level,
|
||||
settingName,
|
||||
}: {
|
||||
category: string
|
||||
settingName: string
|
||||
setting: Setting<unknown>
|
||||
level: string
|
||||
}) {
|
||||
navigate(`?tab=${level}#${settingName}`)
|
||||
function handleSelection({ level, name }: SettingsSearchItem) {
|
||||
navigate(`?tab=${level}#${name}`)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -87,18 +102,18 @@ export function SettingsSearchBar() {
|
||||
<Combobox.Options className="absolute top-full mt-2 right-0 w-80 overflow-y-auto z-50 max-h-96 cursor-pointer bg-chalkboard-10 dark:bg-chalkboard-100 border border-solid border-primary dark:border-chalkboard-30 rounded">
|
||||
{searchResults?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={`${option.category}-${option.settingName}-${option.level}`}
|
||||
key={`${option.category}-${option.name}-${option.level}`}
|
||||
value={option}
|
||||
className="flex flex-col items-start gap-2 px-4 py-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
<p className="flex-grow text-base capitalize m-0 leading-none">
|
||||
{option.level} ·{' '}
|
||||
{decamelize(option.category, { separator: ' ' })} ·{' '}
|
||||
{option.settingNameDisplay}
|
||||
{option.displayName}
|
||||
</p>
|
||||
{option.setting.description && (
|
||||
{option.description && (
|
||||
<p className="text-xs leading-tight text-chalkboard-70 dark:text-chalkboard-50">
|
||||
{option.setting.description}
|
||||
{option.description}
|
||||
</p>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
|
Reference in New Issue
Block a user