Basic editable (not persisted) keybindings
This commit is contained in:
48
src-tauri/Cargo.lock
generated
48
src-tauri/Cargo.lock
generated
@ -188,7 +188,7 @@ dependencies = [
|
|||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -727,7 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
|
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -798,9 +798,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
|
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -808,9 +808,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
|
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -822,9 +822,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
|
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -1389,7 +1389,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg 0.52.0",
|
"winreg 0.52.0",
|
||||||
]
|
]
|
||||||
@ -2622,7 +2622,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"tower-lsp",
|
"tower-lsp",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"url",
|
"url",
|
||||||
@ -5088,7 +5088,7 @@ dependencies = [
|
|||||||
"cfg-expr",
|
"cfg-expr",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5241,7 +5241,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"tauri-winres",
|
"tauri-winres",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5299,7 +5299,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5583,7 +5583,7 @@ dependencies = [
|
|||||||
"serde_with",
|
"serde_with",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
@ -5830,21 +5830,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.16"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_edit 0.22.17",
|
"toml_edit 0.22.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.7"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -5886,15 +5886,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.17"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.6",
|
"winnow 0.6.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6965,9 +6965,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.6"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { isSingleCursorInPipe } from 'lang/queryAst'
|
|||||||
import { useShouldDisableModelingActions } from 'hooks/useShouldDisableModelingActions'
|
import { useShouldDisableModelingActions } from 'hooks/useShouldDisableModelingActions'
|
||||||
import { useInteractionMap } from 'hooks/useInteractionMap'
|
import { useInteractionMap } from 'hooks/useInteractionMap'
|
||||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { KEYBINDING_CATEGORIES } from 'lib/constants'
|
import { KEYBINDING_CATEGORIES } from 'lib/constants'
|
||||||
import { useAppState } from 'AppState'
|
import { useAppState } from 'AppState'
|
||||||
@ -30,7 +29,6 @@ export function Toolbar({
|
|||||||
}: React.HTMLAttributes<HTMLElement>) {
|
}: React.HTMLAttributes<HTMLElement>) {
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const shouldDisableModelingActions = useShouldDisableModelingActions()
|
|
||||||
useInteractionMap(
|
useInteractionMap(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -39,7 +37,7 @@ export function Toolbar({
|
|||||||
sequence: 'shift+s',
|
sequence: 'shift+s',
|
||||||
action: () =>
|
action: () =>
|
||||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } }),
|
send({ type: 'Enter sketch', data: { forceNewSketch: true } }),
|
||||||
guard: () => !shouldDisableModelingActions && state.matches('idle'),
|
guard: () => state.can('Enter sketch'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extrude',
|
name: 'extrude',
|
||||||
@ -50,10 +48,10 @@ export function Toolbar({
|
|||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Extrude', groupId: 'modeling' },
|
data: { name: 'Extrude', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
guard: () => !shouldDisableModelingActions && state.matches('idle'),
|
guard: () => state.can('Extrude'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[shouldDisableModelingActions, commandBarSend, state],
|
[commandBarSend, state],
|
||||||
KEYBINDING_CATEGORIES.MODELING
|
KEYBINDING_CATEGORIES.MODELING
|
||||||
)
|
)
|
||||||
const iconClassName =
|
const iconClassName =
|
||||||
@ -286,19 +284,19 @@ const ToolbarItemContents = memo(function ToolbarItemContents({
|
|||||||
itemConfig: ToolbarItemResolved
|
itemConfig: ToolbarItemResolved
|
||||||
configCallbackProps: ToolbarItemCallbackProps
|
configCallbackProps: ToolbarItemCallbackProps
|
||||||
}) {
|
}) {
|
||||||
useHotkeys(
|
// useHotkeys(
|
||||||
itemConfig.hotkey || '',
|
// itemConfig.hotkey || '',
|
||||||
() => {
|
// () => {
|
||||||
itemConfig.onClick(configCallbackProps)
|
// itemConfig.onClick(configCallbackProps)
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
enabled:
|
// enabled:
|
||||||
itemConfig.status === 'available' &&
|
// itemConfig.status === 'available' &&
|
||||||
!!itemConfig.hotkey &&
|
// !!itemConfig.hotkey &&
|
||||||
!itemConfig.disabled &&
|
// !itemConfig.disabled &&
|
||||||
!itemConfig.disableHotkey,
|
// !itemConfig.disableHotkey,
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
MouseButtonName,
|
MouseButtonName,
|
||||||
interactionMapMachine,
|
interactionMapMachine,
|
||||||
|
makeOverrideKey,
|
||||||
} from 'machines/interactionMapMachine'
|
} from 'machines/interactionMapMachine'
|
||||||
import { createContext, useEffect } from 'react'
|
import { createContext, useEffect } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
@ -61,7 +62,9 @@ export function InteractionMapMachineProvider({
|
|||||||
// normalize any interaction sequences to be sorted
|
// normalize any interaction sequences to be sorted
|
||||||
const normalizedInteractions = event.data.map((item) => ({
|
const normalizedInteractions = event.data.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
sequence: item.sequence
|
sequence: (
|
||||||
|
context.overrides[makeOverrideKey(item)] || item.sequence
|
||||||
|
)
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map((step) =>
|
.map((step) =>
|
||||||
step
|
step
|
||||||
@ -94,6 +97,17 @@ export function InteractionMapMachineProvider({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
'Merge into overrides': assign({
|
||||||
|
overrides: (context, event) => {
|
||||||
|
return {
|
||||||
|
...context.overrides,
|
||||||
|
...event.data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Persist keybinding overrides': (context) => {
|
||||||
|
console.log('Persisting keybinding overrides', context.overrides)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
'Resolve hotkey by prefix': (context, event) => {
|
'Resolve hotkey by prefix': (context, event) => {
|
||||||
@ -111,13 +125,16 @@ export function InteractionMapMachineProvider({
|
|||||||
resolvedInteraction.asString
|
resolvedInteraction.asString
|
||||||
|
|
||||||
const matches = context.interactionMap.filter((item) =>
|
const matches = context.interactionMap.filter((item) =>
|
||||||
item.sequence.startsWith(searchString)
|
(
|
||||||
|
context.overrides[makeOverrideKey(item)] || item.sequence
|
||||||
|
).startsWith(searchString)
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('matches', {
|
console.log('matches', {
|
||||||
matches,
|
matches,
|
||||||
interactionMap: context.interactionMap,
|
interactionMap: context.interactionMap,
|
||||||
searchString,
|
searchString,
|
||||||
|
overrides: context.overrides,
|
||||||
})
|
})
|
||||||
|
|
||||||
// If we have no matches, reject the promise
|
// If we have no matches, reject the promise
|
||||||
@ -126,8 +143,11 @@ export function InteractionMapMachineProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exactMatches = matches.filter(
|
const exactMatches = matches.filter(
|
||||||
(item) => item.sequence === searchString
|
(item) =>
|
||||||
|
(context.overrides[makeOverrideKey(item)] || item.sequence) ===
|
||||||
|
searchString
|
||||||
)
|
)
|
||||||
|
console.log('exactMatches', exactMatches)
|
||||||
if (!exactMatches.length) {
|
if (!exactMatches.length) {
|
||||||
// We have a prefix match.
|
// We have a prefix match.
|
||||||
// Reject the promise and return the step
|
// Reject the promise and return the step
|
||||||
@ -136,9 +156,11 @@ export function InteractionMapMachineProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve to just one exact match
|
// Resolve to just one exact match
|
||||||
const availableExactMatches = exactMatches.filter((item) =>
|
const availableExactMatches = exactMatches.filter(
|
||||||
item.guard(event.data)
|
(item) => !item.guard || item.guard(event.data)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log('availableExactMatches', availableExactMatches)
|
||||||
if (availableExactMatches.length === 0) {
|
if (availableExactMatches.length === 0) {
|
||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export type SidebarType =
|
|||||||
| 'lspMessages'
|
| 'lspMessages'
|
||||||
| 'variables'
|
| 'variables'
|
||||||
|
|
||||||
const PANE_KEYBINDING_PREFIX = 'alt+p ' as const
|
const PANE_KEYBINDING_PREFIX = 'ctrl+shift+p ' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface can be extended as more context is needed for the panes
|
* This interface can be extended as more context is needed for the panes
|
||||||
|
|||||||
@ -12,6 +12,10 @@ import { CustomIconName } from 'components/CustomIcon'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
import { KEYBINDING_CATEGORIES } from 'lib/constants'
|
||||||
|
import { useInteractionMap } from 'hooks/useInteractionMap'
|
||||||
|
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
||||||
|
import { InteractionSequence } from 'components/Settings/AllKeybindingsFields'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -63,6 +67,18 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
[sidebarPanes, showDebugPanel.current]
|
[sidebarPanes, showDebugPanel.current]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useInteractionMap(
|
||||||
|
filteredPanes.map((pane) => ({
|
||||||
|
name: pane.id,
|
||||||
|
action: () => togglePane(pane.id),
|
||||||
|
keybinding: pane.keybinding,
|
||||||
|
title: `Toggle ${pane.title} pane`,
|
||||||
|
sequence: pane.keybinding,
|
||||||
|
})),
|
||||||
|
[filteredPanes, context.store?.openPanes],
|
||||||
|
KEYBINDING_CATEGORIES.USER_INTERFACE
|
||||||
|
)
|
||||||
|
|
||||||
const paneBadgeMap: Record<SidebarType, number | boolean> = useMemo(() => {
|
const paneBadgeMap: Record<SidebarType, number | boolean> = useMemo(() => {
|
||||||
return filteredPanes.reduce((acc, pane) => {
|
return filteredPanes.reduce((acc, pane) => {
|
||||||
if (pane.showBadge) {
|
if (pane.showBadge) {
|
||||||
@ -207,6 +223,16 @@ function ModelingPaneButton({
|
|||||||
showBadge,
|
showBadge,
|
||||||
...props
|
...props
|
||||||
}: ModelingPaneButtonProps) {
|
}: ModelingPaneButtonProps) {
|
||||||
|
const { state: interactionMapState } = useInteractionMapContext()
|
||||||
|
|
||||||
|
const resolvedKeybinding = useMemo(
|
||||||
|
() =>
|
||||||
|
interactionMapState.context.overrides[
|
||||||
|
`${KEYBINDING_CATEGORIES.USER_INTERFACE}.${paneConfig.id}`
|
||||||
|
] || paneConfig.keybinding,
|
||||||
|
[interactionMapState.context.overrides]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
className="pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||||
@ -258,7 +284,10 @@ function ModelingPaneButton({
|
|||||||
{paneConfig.title}
|
{paneConfig.title}
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
</span>
|
</span>
|
||||||
<kbd className="hotkey text-xs capitalize">{paneConfig.keybinding}</kbd>
|
<InteractionSequence
|
||||||
|
sequence={resolvedKeybinding}
|
||||||
|
className="flex-nowrap !gap-1"
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
||||||
|
import { resolveInteractionEvent } from 'lib/keyboard'
|
||||||
import {
|
import {
|
||||||
isModifierKey,
|
InteractionMapItem,
|
||||||
mapKey,
|
makeOverrideKey,
|
||||||
mouseButtonToName,
|
} from 'machines/interactionMapMachine'
|
||||||
resolveInteractionEvent,
|
import { FormEvent, HTMLProps, useEffect, useRef, useState } from 'react'
|
||||||
} from 'lib/keyboard'
|
|
||||||
import { InteractionMapItem } from 'machines/interactionMapMachine'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
export function AllKeybindingsFields() {
|
export function AllKeybindingsFields() {
|
||||||
const { state } = useInteractionMapContext()
|
const { state } = useInteractionMapContext()
|
||||||
@ -23,8 +21,23 @@ export function AllKeybindingsFields() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function KeybindingField({ item }: { item: InteractionMapItem }) {
|
function KeybindingField({ item }: { item: InteractionMapItem }) {
|
||||||
|
const { send, state } = useInteractionMapContext()
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [newSequence, setNewSequence] = useState('')
|
const [newSequence, setNewSequence] = useState('')
|
||||||
|
const submitRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
function handleSubmit(e: FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (newSequence !== item.sequence) {
|
||||||
|
send({
|
||||||
|
type: 'Update overrides',
|
||||||
|
data: {
|
||||||
|
[makeOverrideKey(item)]: newSequence,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const blockOtherEvents = (e: KeyboardEvent | MouseEvent) => {
|
const blockOtherEvents = (e: KeyboardEvent | MouseEvent) => {
|
||||||
@ -34,13 +47,25 @@ function KeybindingField({ item }: { item: InteractionMapItem }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleInteraction = (e: KeyboardEvent | MouseEvent) => {
|
const handleInteraction = (e: KeyboardEvent | MouseEvent) => {
|
||||||
|
if (e instanceof KeyboardEvent && e.key === 'Escape') {
|
||||||
|
blockOtherEvents(e)
|
||||||
|
setIsEditing(false)
|
||||||
|
return
|
||||||
|
} else if (e instanceof KeyboardEvent && e.key === 'Enter') {
|
||||||
|
return
|
||||||
|
} else if (e instanceof MouseEvent && e.target === submitRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
blockOtherEvents(e)
|
blockOtherEvents(e)
|
||||||
|
|
||||||
const resolvedInteraction = resolveInteractionEvent(e)
|
const resolvedInteraction = resolveInteractionEvent(e)
|
||||||
if (resolvedInteraction.isModifier) return
|
if (resolvedInteraction.isModifier) return
|
||||||
setNewSequence(
|
setNewSequence((prev) => {
|
||||||
(prev) => prev + (prev.length ? ' ' : '') + resolvedInteraction.asString
|
const newSequence =
|
||||||
)
|
prev + (prev.length ? ' ' : '') + resolvedInteraction.asString
|
||||||
|
console.log('newSequence', newSequence)
|
||||||
|
return newSequence
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContextMenu = (e: MouseEvent) => {
|
const handleContextMenu = (e: MouseEvent) => {
|
||||||
@ -49,47 +74,100 @@ function KeybindingField({ item }: { item: InteractionMapItem }) {
|
|||||||
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
setNewSequence('')
|
setNewSequence('')
|
||||||
globalThis?.window?.removeEventListener('keydown', handleInteraction)
|
globalThis?.window?.removeEventListener('keydown', handleInteraction, {
|
||||||
globalThis?.window?.removeEventListener('mousedown', handleInteraction)
|
capture: true,
|
||||||
globalThis?.window?.removeEventListener('contextmenu', handleContextMenu)
|
})
|
||||||
|
globalThis?.window?.removeEventListener('mousedown', handleInteraction, {
|
||||||
|
capture: true,
|
||||||
|
})
|
||||||
|
globalThis?.window?.removeEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
handleContextMenu,
|
||||||
|
{ capture: true }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
globalThis?.window?.addEventListener('keydown', handleInteraction)
|
globalThis?.window?.addEventListener('keydown', handleInteraction, {
|
||||||
globalThis?.window?.addEventListener('mousedown', handleInteraction)
|
capture: true,
|
||||||
globalThis?.window?.addEventListener('contextmenu', handleContextMenu)
|
})
|
||||||
|
globalThis?.window?.addEventListener('mousedown', handleInteraction, {
|
||||||
|
capture: true,
|
||||||
|
})
|
||||||
|
globalThis?.window?.addEventListener('contextmenu', handleContextMenu, {
|
||||||
|
capture: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
globalThis?.window?.removeEventListener('keydown', handleInteraction)
|
globalThis?.window?.removeEventListener('keydown', handleInteraction, {
|
||||||
globalThis?.window?.removeEventListener('mousedown', handleInteraction)
|
capture: true,
|
||||||
globalThis?.window?.removeEventListener('contextmenu', handleContextMenu)
|
})
|
||||||
|
globalThis?.window?.removeEventListener('mousedown', handleInteraction, {
|
||||||
|
capture: true,
|
||||||
|
})
|
||||||
|
globalThis?.window?.removeEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
handleContextMenu,
|
||||||
|
{ capture: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [isEditing])
|
}, [isEditing, setNewSequence])
|
||||||
|
|
||||||
return (
|
return isEditing ? (
|
||||||
|
<form
|
||||||
|
key={item.ownerId + '-' + item.name}
|
||||||
|
className="flex gap-2 justify-between items-start"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<h3>{item.title}</h3>
|
||||||
|
<InteractionSequence sequence={newSequence} showNoSequence />
|
||||||
|
<input type="hidden" value={item.sequence} name="sequence" />
|
||||||
|
<button ref={submitRef} className="p-0 m-0" type="submit">
|
||||||
|
<ActionIcon icon="checkmark" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
<div
|
<div
|
||||||
key={item.ownerId + '-' + item.name}
|
key={item.ownerId + '-' + item.name}
|
||||||
className="flex gap-2 justify-between items-start"
|
className="flex gap-2 justify-between items-start"
|
||||||
>
|
>
|
||||||
<h3>{item.title}</h3>
|
<h3>{item.title}</h3>
|
||||||
<div className="flex-1 flex flex-wrap justify-end gap-3">
|
<InteractionSequence
|
||||||
{(isEditing ? newSequence : item.sequence)
|
sequence={
|
||||||
.split(' ')
|
state.context.overrides[makeOverrideKey(item)] || item.sequence
|
||||||
.map((chord, i) => (
|
}
|
||||||
<kbd
|
showNoSequence
|
||||||
key={`${item.ownerId}-${item.name}-${chord}-${i}`}
|
/>
|
||||||
className="py-0.5 px-1.5 rounded bg-primary/10 dark:bg-chalkboard-80"
|
|
||||||
>
|
|
||||||
{chord}
|
|
||||||
</kbd>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditing((prev) => !prev)}
|
ref={submitRef}
|
||||||
className="p-0 m-0"
|
className="p-0 m-0"
|
||||||
type={isEditing ? 'submit' : 'button'}
|
onClick={() => setIsEditing(true)}
|
||||||
>
|
>
|
||||||
<ActionIcon icon={isEditing ? 'checkmark' : 'sketch'} />
|
<ActionIcon icon="sketch" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function InteractionSequence({
|
||||||
|
sequence,
|
||||||
|
className = '',
|
||||||
|
showNoSequence = false,
|
||||||
|
...props
|
||||||
|
}: HTMLProps<HTMLDivElement> & { sequence: string; showNoSequence?: boolean }) {
|
||||||
|
return sequence.length ? (
|
||||||
|
<div
|
||||||
|
className={'flex-1 flex flex-wrap justify-end gap-3 ' + className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{sequence.split(' ').map((chord, i) => (
|
||||||
|
<kbd key={`sequence-${sequence}-${chord}-${i}`} className="hotkey">
|
||||||
|
{chord}
|
||||||
|
</kbd>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
showNoSequence && (
|
||||||
|
<div className="flex-1 flex justify-end text-xs">No sequence set</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import {
|
import { useAppState } from 'AppState'
|
||||||
NetworkHealthState,
|
import { NetworkHealthState, useNetworkStatus } from 'hooks/useNetworkStatus'
|
||||||
useNetworkStatus,
|
|
||||||
} from 'components/NetworkHealthIndicator'
|
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook to determine if modeling actions should be disabled
|
* Custom hook to determine if modeling actions should be disabled
|
||||||
@ -13,9 +10,7 @@ import { useStore } from 'useStore'
|
|||||||
export function useShouldDisableModelingActions() {
|
export function useShouldDisableModelingActions() {
|
||||||
const { overallState } = useNetworkStatus()
|
const { overallState } = useNetworkStatus()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { isStreamReady } = useStore((s) => ({
|
const { isStreamReady } = useAppState()
|
||||||
isStreamReady: s.isStreamReady,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
|
return overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,4 +61,5 @@ export const KCL_DEFAULT_LENGTH = `5`
|
|||||||
export const KEYBINDING_CATEGORIES = {
|
export const KEYBINDING_CATEGORIES = {
|
||||||
MODELING: 'modeling',
|
MODELING: 'modeling',
|
||||||
COMMAND_BAR: 'command-bar',
|
COMMAND_BAR: 'command-bar',
|
||||||
|
USER_INTERFACE: 'user-interface',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,15 +6,20 @@ export type InteractionMapItem = {
|
|||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
sequence: string
|
sequence: string
|
||||||
guard: (e: MouseEvent | KeyboardEvent) => boolean
|
guard?: (e: MouseEvent | KeyboardEvent) => boolean
|
||||||
action: () => void
|
action: () => void
|
||||||
ownerId: string
|
ownerId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeOverrideKey(interactionMapItem: InteractionMapItem) {
|
||||||
|
return `${interactionMapItem.ownerId}.${interactionMapItem.name}`
|
||||||
|
}
|
||||||
|
|
||||||
export const interactionMapMachine = createMachine({
|
export const interactionMapMachine = createMachine({
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPU9UGICA4FUCj6zWaismlWC5Xg0YhncRBfH0csdRTWwTEMaIbF0TRa3OYzL1xXZ9k-I4TiwM4MXnYSLJUKz-iQwwTFwzQEvhM1bC3DTVSBTzHBNdzvPC+1GyvFsuTJPkaXM2VorEhVj1+CRdBMctLHUk03JwzR-HLHMLECDyEu8fK7SxIrcVo4CGL499HnQSqIraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPJZznrNZqKyZgAFVqAgANikKb1CAgOAhITUT1EQbUVRBA9gRsEw1SGdwvHLExkLLCR1yCzVyxPU9UGIGz4FeCi9MvLJpVguV4NGbyRl8fRyx1XCTDBQxNH+MJa10i9GyvXZ9k-I4TiwM4MXnYTkpUVL-iQwwTFwzROvhM1bC3DTVSBXQHFsHVtBsEqGvtcrcRbLkyT5GkktlFqxIVY9fiCzyzXUk1DGwnyEGVfxyxzCxAhsXROu8Ka7SxWanVo4CGL499HnQFbGraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
||||||
context: {
|
context: {
|
||||||
interactionMap: [] as InteractionMapItem[],
|
interactionMap: [] as InteractionMapItem[],
|
||||||
|
overrides: {} as { [key: string]: string },
|
||||||
currentSequence: '' as string,
|
currentSequence: '' as string,
|
||||||
},
|
},
|
||||||
predictableActionArguments: true,
|
predictableActionArguments: true,
|
||||||
@ -35,6 +40,7 @@ export const interactionMapMachine = createMachine({
|
|||||||
| { type: 'Update prefix matrix' }
|
| { type: 'Update prefix matrix' }
|
||||||
| { type: 'Add last interaction to sequence' }
|
| { type: 'Add last interaction to sequence' }
|
||||||
| { type: 'Clear sequence' }
|
| { type: 'Clear sequence' }
|
||||||
|
| { type: 'Update overrides'; data: { [key: string]: string } }
|
||||||
| { type: 'Resolve hotkey by prefix'; data: MouseEvent | KeyboardEvent }
|
| { type: 'Resolve hotkey by prefix'; data: MouseEvent | KeyboardEvent }
|
||||||
| { type: 'done.invoke.resolveHotkeyByPrefix'; data: InteractionMapItem }
|
| { type: 'done.invoke.resolveHotkeyByPrefix'; data: InteractionMapItem }
|
||||||
| {
|
| {
|
||||||
@ -115,5 +121,11 @@ export const interactionMapMachine = createMachine({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Update overrides': {
|
||||||
|
target: '#Interaction Map Actor',
|
||||||
|
internal: true,
|
||||||
|
actions: ['Merge into overrides', 'Persist keybinding overrides'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user