Proof of concept of global interactionMap
This commit is contained in:
124
src-tauri/Cargo.lock
generated
124
src-tauri/Cargo.lock
generated
@ -169,9 +169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -334,7 +334,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -369,7 +369,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -415,7 +415,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -564,7 +564,7 @@ dependencies = [
|
||||
"proc-macro-crate 3.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"syn_derive",
|
||||
]
|
||||
|
||||
@ -873,7 +873,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1075,7 +1075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1085,7 +1085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1109,7 +1109,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1120,7 +1120,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1167,7 +1167,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -1204,7 +1204,7 @@ dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1287,7 +1287,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1385,7 +1385,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1536,7 +1536,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1652,7 +1652,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1928,7 +1928,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1956,7 +1956,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2031,7 +2031,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2640,7 +2640,7 @@ checksum = "0611fc9b9786175da21d895ffa0f65039e19c9111e94a41b7af999e3b95f045f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3362,7 +3362,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.7.5",
|
||||
"structmeta 0.2.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3376,7 +3376,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.3",
|
||||
"structmeta 0.3.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3518,7 +3518,7 @@ dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3586,7 +3586,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4424,7 +4424,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4509,9 +4509,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.200"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -4527,13 +4527,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.200"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4544,7 +4544,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4577,7 +4577,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4598,7 +4598,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4640,7 +4640,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4908,7 +4908,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive 0.2.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4920,7 +4920,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive 0.3.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4931,7 +4931,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4942,7 +4942,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4964,7 +4964,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4997,9 +4997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
version = "2.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -5015,7 +5015,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5032,7 +5032,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5249,7 +5249,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time",
|
||||
@ -5267,7 +5267,7 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"tauri-codegen",
|
||||
"tauri-utils",
|
||||
]
|
||||
@ -5602,22 +5602,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5706,7 +5706,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5895,7 +5895,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5924,7 +5924,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6048,7 +6048,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -6261,7 +6261,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6360,7 +6360,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -6394,7 +6394,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -6535,7 +6535,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6641,7 +6641,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6652,7 +6652,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7094,7 +7094,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -31,6 +31,7 @@ import LspProvider from 'components/LspProvider'
|
||||
import { KclContextProvider } from 'lang/KclProvider'
|
||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||
import { getState, setState } from 'lib/tauri'
|
||||
import { InteractionMapMachineProvider } from 'components/InteractionMapMachineProvider'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -39,15 +40,17 @@ const router = createBrowserRouter([
|
||||
/* Make sure auth is the outermost provider or else we will have
|
||||
* inefficient re-renders, use the react profiler to see. */
|
||||
element: (
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<KclContextProvider>
|
||||
<Outlet />
|
||||
</KclContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
</CommandBarProvider>
|
||||
<InteractionMapMachineProvider>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<KclContextProvider>
|
||||
<Outlet />
|
||||
</KclContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
</CommandBarProvider>
|
||||
</InteractionMapMachineProvider>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
|
||||
@ -11,10 +11,29 @@ import {
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { useStore } from 'useStore'
|
||||
import { useShouldDisableModelingActions } from 'hooks/useShouldDisableModelingActions'
|
||||
import { useInteractionMap } from 'hooks/useInteractionMap'
|
||||
|
||||
export const Toolbar = () => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const shouldDisableModelingActions = useShouldDisableModelingActions()
|
||||
useInteractionMap(
|
||||
[
|
||||
{
|
||||
name: 'extrude',
|
||||
title: 'Extrude',
|
||||
sequence: 'shift+e',
|
||||
action: () =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
}),
|
||||
guard: () => !shouldDisableModelingActions && state.matches('idle'),
|
||||
},
|
||||
],
|
||||
[shouldDisableModelingActions, commandBarSend, state]
|
||||
)
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
const iconClassName =
|
||||
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
|
||||
|
||||
@ -113,8 +113,6 @@ function CommandArgOptionInput({
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (event.metaKey && event.key === 'k')
|
||||
commandBarSend({ type: 'Close' })
|
||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||
stepBack()
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { Fragment, useEffect, useMemo } from 'react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import CommandBarArgument from './CommandBarArgument'
|
||||
import CommandComboBox from '../CommandComboBox'
|
||||
import CommandBarReview from './CommandBarReview'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { InteractionMapItem } from 'machines/interactionMapMachine'
|
||||
import { useInteractionMap } from 'hooks/useInteractionMap'
|
||||
|
||||
export const CommandBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
@ -21,15 +22,35 @@ export const CommandBar = () => {
|
||||
commandBarSend({ type: 'Close' })
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeys(['mod+k', 'mod+/'], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarSend({ type: 'Open' })
|
||||
} else {
|
||||
commandBarSend({ type: 'Close' })
|
||||
}
|
||||
})
|
||||
useInteractionMap(
|
||||
[
|
||||
{
|
||||
name: 'toggle',
|
||||
title: 'Toggle Command Bar',
|
||||
sequence: 'meta+k',
|
||||
action: () => {
|
||||
const type = commandBarState.matches('Closed') ? 'Open' : 'Close'
|
||||
console.log('toggling command bar', type)
|
||||
commandBarSend({
|
||||
type,
|
||||
})
|
||||
},
|
||||
guard: () => true,
|
||||
ownerId: 'commandBar',
|
||||
},
|
||||
{
|
||||
name: 'close',
|
||||
title: 'Close Command Bar',
|
||||
sequence: 'esc',
|
||||
action: () => {
|
||||
commandBarSend({ type: 'Close' })
|
||||
},
|
||||
guard: () => !commandBarState.matches('Closed'),
|
||||
ownerId: 'commandBar',
|
||||
},
|
||||
],
|
||||
[commandBarState, commandBarSend]
|
||||
)
|
||||
|
||||
function stepBack() {
|
||||
if (!currentArgument) {
|
||||
|
||||
@ -15,8 +15,7 @@ function CommandBarBasicInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (event: unknown) => void
|
||||
}) {
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
const { commandBarState } = useCommandsContext()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -25,7 +25,7 @@ function CommandBarKclInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (event: unknown) => void
|
||||
}) {
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
const { commandBarState } = useCommandsContext()
|
||||
const previouslySetValue = commandBarState.context.argumentsToSubmit[
|
||||
arg.name
|
||||
] as KclCommandValue | undefined
|
||||
@ -38,7 +38,6 @@ function CommandBarKclInput({
|
||||
previouslySetValue && 'variableName' in previouslySetValue
|
||||
)
|
||||
const [canSubmit, setCanSubmit] = useState(true)
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
const editorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
|
||||
@ -43,15 +43,6 @@ function CommandComboBox({
|
||||
<Combobox.Input
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
(event.metaKey && event.key === 'k') ||
|
||||
(event.key === 'Backspace' && !event.currentTarget.value)
|
||||
) {
|
||||
event.preventDefault()
|
||||
commandBarSend({ type: 'Close' })
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
(defaultOption && defaultOption.name) ||
|
||||
placeholder ||
|
||||
|
||||
206
src/components/InteractionMapMachineProvider.tsx
Normal file
206
src/components/InteractionMapMachineProvider.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
||||
import { isModifierKey, mapKey, sortKeys } from 'lib/keyboard'
|
||||
import {
|
||||
MouseButtonName,
|
||||
interactionMapMachine,
|
||||
} from 'machines/interactionMapMachine'
|
||||
import { createContext, useEffect } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
AnyStateMachine,
|
||||
StateFrom,
|
||||
Prop,
|
||||
InterpreterFrom,
|
||||
assign,
|
||||
} from 'xstate'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
send: Prop<InterpreterFrom<T>, 'send'>
|
||||
}
|
||||
|
||||
export const InteractionMapMachineContext = createContext(
|
||||
{} as MachineContext<typeof interactionMapMachine>
|
||||
)
|
||||
|
||||
export function InteractionMapMachineProvider({
|
||||
children,
|
||||
}: React.PropsWithChildren<{}>) {
|
||||
const [state, send] = useMachine(interactionMapMachine, {
|
||||
logger: (msg) => {
|
||||
console.log(msg)
|
||||
},
|
||||
actions: {
|
||||
'Add last interaction to sequence': assign({
|
||||
currentSequence: (context, event) => {
|
||||
const newSequence = event.data
|
||||
? context.currentSequence
|
||||
? context.currentSequence.concat(' ', event.data)
|
||||
: event.data
|
||||
: context.currentSequence
|
||||
|
||||
console.log('newSequence', newSequence)
|
||||
return newSequence
|
||||
},
|
||||
}),
|
||||
'Clear sequence': assign({
|
||||
currentSequence: () => {
|
||||
console.log('clearing sequence')
|
||||
return ''
|
||||
},
|
||||
}),
|
||||
'Add to interactionMap': assign({
|
||||
interactionMap: (context, event) => {
|
||||
// normalize any interaction sequences to be sorted
|
||||
const normalizedInteractions = event.data.map((item) => ({
|
||||
...item,
|
||||
sequence: item.sequence
|
||||
.split(' ')
|
||||
.map((step) =>
|
||||
step
|
||||
.split(INTERACTION_MAP_SEPARATOR)
|
||||
.sort(sortKeys)
|
||||
.map(mapKey)
|
||||
.join(INTERACTION_MAP_SEPARATOR)
|
||||
)
|
||||
.join(' '),
|
||||
}))
|
||||
|
||||
// Add the new items to the interactionMap and sort by sequence
|
||||
// making it faster to search for a sequence
|
||||
const newInteractionMap = [
|
||||
...context.interactionMap,
|
||||
...normalizedInteractions,
|
||||
].sort((a, b) => a.sequence.localeCompare(b.sequence))
|
||||
|
||||
console.log('newInteractionMap', newInteractionMap)
|
||||
return newInteractionMap
|
||||
},
|
||||
}),
|
||||
'Remove from interactionMap': assign({
|
||||
interactionMap: (context, event) => {
|
||||
// Filter out any items that have an ownerId that matches event.data
|
||||
return [
|
||||
...context.interactionMap.filter(
|
||||
(item) => item.ownerId !== event.data
|
||||
),
|
||||
]
|
||||
},
|
||||
}),
|
||||
},
|
||||
services: {
|
||||
'Resolve hotkey by prefix': (context, event) => {
|
||||
// First determine if we have a mouse or keyboard event
|
||||
const action =
|
||||
'key' in event.data
|
||||
? mapKey(event.data.code)
|
||||
: mouseButtonToName(event.data.button)
|
||||
|
||||
// if the key is already a modifier key, skip everything else and reject
|
||||
if (isModifierKey(action)) {
|
||||
// We return an empty string so that we don't clear the currentSequence
|
||||
return Promise.reject('')
|
||||
}
|
||||
|
||||
const modifiers = [
|
||||
event.data.ctrlKey && 'ctrl',
|
||||
event.data.shiftKey && 'shift',
|
||||
event.data.altKey && 'alt',
|
||||
event.data.metaKey && 'meta',
|
||||
].filter((item) => item !== false) as string[]
|
||||
const step = [action, ...modifiers]
|
||||
.sort(sortKeys)
|
||||
.join(INTERACTION_MAP_SEPARATOR)
|
||||
|
||||
// Find all the sequences that start with the current sequence
|
||||
const searchString =
|
||||
(context.currentSequence ? context.currentSequence + ' ' : '') + step
|
||||
|
||||
const matches = context.interactionMap.filter((item) =>
|
||||
item.sequence.startsWith(searchString)
|
||||
)
|
||||
|
||||
// If we have no matches, reject the promise
|
||||
if (matches.length === 0) {
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
const exactMatches = matches.filter(
|
||||
(item) => item.sequence === searchString
|
||||
)
|
||||
if (!exactMatches.length) {
|
||||
// We have a prefix match.
|
||||
// Reject the promise and return the step
|
||||
// so we can add it to currentSequence
|
||||
return Promise.reject(step)
|
||||
}
|
||||
|
||||
// Resolve to just one exact match
|
||||
const availableExactMatches = exactMatches.filter((item) =>
|
||||
item.guard(event.data)
|
||||
)
|
||||
if (availableExactMatches.length === 0) {
|
||||
return Promise.reject()
|
||||
} else {
|
||||
// return the last-added, available exact match
|
||||
return Promise.resolve(
|
||||
availableExactMatches[availableExactMatches.length - 1]
|
||||
)
|
||||
}
|
||||
},
|
||||
'Execute keymap action': async (_context, event) => {
|
||||
try {
|
||||
console.log('Executing action', event.data)
|
||||
event.data.action()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.error('There was an error executing the action.')
|
||||
}
|
||||
},
|
||||
},
|
||||
guards: {
|
||||
'There are prefix matches': (_context, event) => {
|
||||
return event.data !== undefined
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Setting up global event listeners
|
||||
useEffect(() => {
|
||||
if (!globalThis || !globalThis.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const fireEvent = (event: MouseEvent | KeyboardEvent) => {
|
||||
send({ type: 'Fire event', data: event })
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', fireEvent)
|
||||
window.addEventListener('mousedown', fireEvent)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', fireEvent)
|
||||
window.removeEventListener('mousedown', fireEvent)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<InteractionMapMachineContext.Provider value={{ state, send }}>
|
||||
{children}
|
||||
</InteractionMapMachineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function mouseButtonToName(button: MouseEvent['button']): MouseButtonName {
|
||||
switch (button) {
|
||||
case 0:
|
||||
return 'LeftButton'
|
||||
case 1:
|
||||
return 'MiddleButton'
|
||||
case 2:
|
||||
return 'RightButton'
|
||||
default:
|
||||
return 'LeftButton'
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,17 @@ import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
|
||||
import { HelpMenu } from './HelpMenu'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
||||
|
||||
function InteractionSequenceInfo() {
|
||||
const {
|
||||
state: {
|
||||
context: { currentSequence },
|
||||
},
|
||||
} = useInteractionMapContext()
|
||||
|
||||
return <span className="font-mono text-xs">{currentSequence}</span>
|
||||
}
|
||||
|
||||
export function LowerRightControls(props: React.PropsWithChildren) {
|
||||
const location = useLocation()
|
||||
@ -17,6 +28,7 @@ export function LowerRightControls(props: React.PropsWithChildren) {
|
||||
<section className="fixed bottom-2 right-2">
|
||||
{props.children}
|
||||
<menu className="flex items-center justify-end gap-3">
|
||||
<InteractionSequenceInfo />
|
||||
<a
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
target="_blank"
|
||||
|
||||
@ -25,6 +25,8 @@ export type SidebarType =
|
||||
| 'lspMessages'
|
||||
| 'variables'
|
||||
|
||||
const PANE_KEYBINDING_PREFIX = 'alt+p ' as const
|
||||
|
||||
export type SidebarPane = {
|
||||
id: SidebarType
|
||||
title: string
|
||||
@ -41,7 +43,7 @@ export const topPanes: SidebarPane[] = [
|
||||
title: 'KCL Code',
|
||||
icon: faCode,
|
||||
Content: KclEditorPane,
|
||||
keybinding: 'shift + c',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'c',
|
||||
Menu: KclEditorMenu,
|
||||
},
|
||||
{
|
||||
@ -49,7 +51,7 @@ export const topPanes: SidebarPane[] = [
|
||||
title: 'Project Files',
|
||||
icon: 'folder',
|
||||
Content: FileTreeInner,
|
||||
keybinding: 'shift + f',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'f',
|
||||
Menu: FileTreeMenu,
|
||||
hideOnPlatform: 'web',
|
||||
},
|
||||
@ -61,27 +63,27 @@ export const bottomPanes: SidebarPane[] = [
|
||||
title: 'Variables',
|
||||
icon: faSquareRootVariable,
|
||||
Content: MemoryPane,
|
||||
keybinding: 'shift + v',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'v',
|
||||
},
|
||||
{
|
||||
id: 'logs',
|
||||
title: 'Logs',
|
||||
icon: faCodeCommit,
|
||||
Content: LogsPane,
|
||||
keybinding: 'shift + l',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'l',
|
||||
},
|
||||
{
|
||||
id: 'kclErrors',
|
||||
title: 'KCL Errors',
|
||||
icon: faExclamationCircle,
|
||||
Content: KclErrorsPane,
|
||||
keybinding: 'shift + e',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'e',
|
||||
},
|
||||
{
|
||||
id: 'debug',
|
||||
title: 'Debug',
|
||||
icon: faBugSlash,
|
||||
Content: DebugPane,
|
||||
keybinding: 'shift + d',
|
||||
keybinding: PANE_KEYBINDING_PREFIX + 'd',
|
||||
},
|
||||
]
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Resizable } from 're-resizable'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useStore } from 'useStore'
|
||||
import { Tab } from '@headlessui/react'
|
||||
import {
|
||||
@ -194,10 +193,6 @@ function ModelingPaneButton({
|
||||
currentPane,
|
||||
togglePane,
|
||||
}: ModelingPaneButtonProps) {
|
||||
useHotkeys(paneConfig.keybinding, togglePane, {
|
||||
scopes: ['modeling'],
|
||||
})
|
||||
|
||||
return (
|
||||
<Tab
|
||||
key={paneConfig.id}
|
||||
|
||||
40
src/hooks/useInteractionMap.ts
Normal file
40
src/hooks/useInteractionMap.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { InteractionMapItem } from 'machines/interactionMapMachine'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useInteractionMapContext } from './useInteractionMapContext'
|
||||
|
||||
/**
|
||||
* Custom hook to add an interaction map to the interaction map machine
|
||||
* from within a component, and remove it when the component unmounts.
|
||||
* @param interactionMap - An array of interaction map items. You don't need to provide an `ownerId` property, as it will be added automatically.
|
||||
* @param deps - Any dependencies that should trigger a resetting of the interaction map when they change.
|
||||
* @param mapId - An optional ID for the interaction map. If not provided, a random UUID will be generated.
|
||||
*/
|
||||
export function useInteractionMap(
|
||||
interactionMap: Omit<InteractionMapItem, 'ownerId'>[],
|
||||
deps: any[],
|
||||
mapId?: string
|
||||
) {
|
||||
const interactionMachine = useInteractionMapContext()
|
||||
const mapIdMemoized = useMemo<string>(
|
||||
() => mapId || crypto.randomUUID(),
|
||||
[mapId]
|
||||
)
|
||||
const interactionMapMemoized = useMemo<InteractionMapItem[]>(
|
||||
() => interactionMap.map((item) => ({ ...item, ownerId: mapIdMemoized })),
|
||||
deps
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
interactionMachine.send({
|
||||
type: 'Add to interaction map',
|
||||
data: interactionMapMemoized,
|
||||
})
|
||||
|
||||
return () => {
|
||||
interactionMachine.send({
|
||||
type: 'Remove from interaction map',
|
||||
data: mapIdMemoized,
|
||||
})
|
||||
}
|
||||
}, [interactionMapMemoized, mapIdMemoized])
|
||||
}
|
||||
6
src/hooks/useInteractionMapContext.ts
Normal file
6
src/hooks/useInteractionMapContext.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { InteractionMapMachineContext } from 'components/InteractionMapMachineProvider'
|
||||
import { useContext } from 'react'
|
||||
|
||||
export const useInteractionMapContext = () => {
|
||||
return useContext(InteractionMapMachineContext)
|
||||
}
|
||||
21
src/hooks/useShouldDisableModelingActions.ts
Normal file
21
src/hooks/useShouldDisableModelingActions.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {
|
||||
NetworkHealthState,
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
/**
|
||||
* Custom hook to determine if modeling actions should be disabled
|
||||
* based on the current network status, KCL execution status, and stream readiness.
|
||||
* @returns boolean
|
||||
*/
|
||||
export function useShouldDisableModelingActions() {
|
||||
const { overallState } = useNetworkStatus()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useStore((s) => ({
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
|
||||
return overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
|
||||
}
|
||||
@ -7,12 +7,7 @@ import { authMachine } from 'machines/authMachine'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
import { Command, CommandSetConfig, CommandSetSchema } from 'lib/commandTypes'
|
||||
import {
|
||||
NetworkHealthState,
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useStore } from 'useStore'
|
||||
import { useShouldDisableModelingActions } from './useShouldDisableModelingActions'
|
||||
|
||||
// This might not be necessary, AnyStateMachine from xstate is working
|
||||
export type AllMachines =
|
||||
@ -47,17 +42,13 @@ export default function useStateMachineCommands<
|
||||
onCancel,
|
||||
}: UseStateMachineCommandsArgs<T, S>) {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { overallState } = useNetworkStatus()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useStore((s) => ({
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const shouldDisableModelingActions = useShouldDisableModelingActions()
|
||||
|
||||
useEffect(() => {
|
||||
const disableAllButtons =
|
||||
overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
|
||||
const newCommands = state.nextEvents
|
||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||
.filter(
|
||||
(_) => !allCommandsRequireNetwork || !shouldDisableModelingActions
|
||||
)
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.map((type) =>
|
||||
createMachineCommand<T, S>({
|
||||
@ -80,5 +71,5 @@ export default function useStateMachineCommands<
|
||||
data: { commands: newCommands },
|
||||
})
|
||||
}
|
||||
}, [state, overallState, isExecuting, isStreamReady])
|
||||
}, [state, shouldDisableModelingActions])
|
||||
}
|
||||
|
||||
@ -42,3 +42,5 @@ export const RELEVANT_FILE_TYPES = [
|
||||
] as const
|
||||
/** The default name for a tutorial project */
|
||||
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
|
||||
/** The separator between keys/buttons in an InteractionMapItem's step */
|
||||
export const INTERACTION_MAP_SEPARATOR = '+'
|
||||
|
||||
44
src/lib/keyboard.ts
Normal file
44
src/lib/keyboard.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* From https://github.com/JohannesKlauss/react-hotkeys-hook/blob/main/src/parseHotkeys.ts
|
||||
* we don't want to use the whole library (as cool as it is) because it attaches
|
||||
* new listeners for each hotkey. Just the key parsing part is good for us.
|
||||
*/
|
||||
const reservedModifierKeywords = ['shift', 'alt', 'meta', 'mod', 'ctrl']
|
||||
|
||||
const mappedKeys: Record<string, string> = {
|
||||
esc: 'escape',
|
||||
return: 'enter',
|
||||
'.': 'period',
|
||||
',': 'comma',
|
||||
'-': 'slash',
|
||||
' ': 'space',
|
||||
'`': 'backquote',
|
||||
'#': 'backslash',
|
||||
'+': 'bracketright',
|
||||
ShiftLeft: 'shift',
|
||||
ShiftRight: 'shift',
|
||||
AltLeft: 'alt',
|
||||
AltRight: 'alt',
|
||||
MetaLeft: 'meta',
|
||||
MetaRight: 'meta',
|
||||
OSLeft: 'meta',
|
||||
OSRight: 'meta',
|
||||
ControlLeft: 'ctrl',
|
||||
ControlRight: 'ctrl',
|
||||
}
|
||||
|
||||
export function mapKey(key: string): string {
|
||||
return (mappedKeys[key] || key)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/key|digit|numpad|arrow/, '')
|
||||
}
|
||||
|
||||
export function isModifierKey(key: string) {
|
||||
return reservedModifierKeywords.includes(key)
|
||||
}
|
||||
|
||||
// Sorts keys in the order of modifier keys, then alphabetically
|
||||
export function sortKeys(a: string, b: string) {
|
||||
return isModifierKey(a) ? -1 : isModifierKey(b) ? 1 : a.localeCompare(b)
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
import { createMachine } from 'xstate'
|
||||
|
||||
export type InteractionMapItem = {
|
||||
name: string
|
||||
title: string
|
||||
sequence: string
|
||||
guard: () => boolean
|
||||
action: () => void
|
||||
}
|
||||
|
||||
export const interactionMachine = createMachine({
|
||||
context: {
|
||||
interactionMap: new Set<InteractionMapItem>(),
|
||||
prefixMatrix: new Set<Set<string>>(),
|
||||
currentSequence: [] as string[],
|
||||
},
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./interactionMachine.typegen').Typegen0,
|
||||
schema: {
|
||||
events: {} as
|
||||
| {
|
||||
type: 'Update interaction map'
|
||||
data: InteractionMapItem[]
|
||||
}
|
||||
| { type: 'Fire event'; data: MouseEvent | KeyboardEvent }
|
||||
| { type: 'Update prefix matrix' }
|
||||
| { type: 'Add last interaction to sequence' }
|
||||
| { type: 'Clear sequence' }
|
||||
| { type: 'Resolve hotkey by prefix'; data: MouseEvent | KeyboardEvent },
|
||||
},
|
||||
id: 'Interaction Map Actor',
|
||||
initial: 'Listening for interaction',
|
||||
on: {
|
||||
'Update interaction map': {
|
||||
target: '#Interaction Map Actor',
|
||||
actions: [
|
||||
{
|
||||
type: 'Update interactionMap',
|
||||
},
|
||||
{
|
||||
type: 'Update prefix matrix',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Manages the keymap of actions that can be take with the keyboard, mouse, or combination of the two while using the app.',
|
||||
states: {
|
||||
'Listening for interaction': {
|
||||
on: {
|
||||
'Fire event': {
|
||||
target: 'Resolve hotkey',
|
||||
},
|
||||
},
|
||||
},
|
||||
'Resolve hotkey': {
|
||||
invoke: {
|
||||
id: 'resolveHotkeyPrefix',
|
||||
onDone: {
|
||||
target: 'Execute keymap event',
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: 'Listening for interaction',
|
||||
actions: {
|
||||
type: 'Add last interaction to sequence',
|
||||
},
|
||||
cond: {
|
||||
type: 'There are prefix matches',
|
||||
},
|
||||
},
|
||||
{
|
||||
target: 'Listening for interaction',
|
||||
actions: {
|
||||
type: 'Clear sequence',
|
||||
},
|
||||
},
|
||||
],
|
||||
src: 'Resolve hotkey by prefix',
|
||||
},
|
||||
},
|
||||
'Execute keymap event': {
|
||||
exit: {
|
||||
type: 'Clear sequence',
|
||||
},
|
||||
invoke: {
|
||||
id: 'executeKeymapAction',
|
||||
onDone: {
|
||||
target: 'Listening for interaction',
|
||||
},
|
||||
onError: {
|
||||
target: 'Listening for interaction',
|
||||
},
|
||||
src: 'Execute keymap action',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
119
src/machines/interactionMapMachine.ts
Normal file
119
src/machines/interactionMapMachine.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { createMachine } from 'xstate'
|
||||
|
||||
export type MouseButtonName = `${'Left' | 'Middle' | 'Right'}Button`
|
||||
|
||||
export type InteractionMapItem = {
|
||||
name: string
|
||||
title: string
|
||||
sequence: string
|
||||
guard: (e: MouseEvent | KeyboardEvent) => boolean
|
||||
action: () => void
|
||||
ownerId: string
|
||||
}
|
||||
|
||||
export const interactionMapMachine = createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPU9UGICA4FUCj6zWaismlWC5Xg0YhncRBfH0csdRTWwTEMaIbF0TRa3OYzL1xXZ9k-I4TiwM4MXnYSLJUKz-iQwwTFwzQEvhM1bC3DTVSBTzHBNdzvPC+1GyvFsuTJPkaXM2VorEhVj1+CRdBMctLHUk03JwzR-HLHMLECDyEu8fK7SxIrcVo4CGL499HnQSqIraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
||||
context: {
|
||||
interactionMap: [] as InteractionMapItem[],
|
||||
currentSequence: '' as string,
|
||||
},
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./interactionMapMachine.typegen').Typegen0,
|
||||
schema: {
|
||||
events: {} as
|
||||
| {
|
||||
type: 'Add to interaction map'
|
||||
data: InteractionMapItem[]
|
||||
}
|
||||
| {
|
||||
type: 'Remove from interaction map'
|
||||
data: string
|
||||
}
|
||||
| { type: 'Fire event'; data: MouseEvent | KeyboardEvent }
|
||||
| { type: 'Execute keymap action'; data: InteractionMapItem }
|
||||
| { type: 'Update prefix matrix' }
|
||||
| { type: 'Add last interaction to sequence' }
|
||||
| { type: 'Clear sequence' }
|
||||
| { type: 'Resolve hotkey by prefix'; data: MouseEvent | KeyboardEvent }
|
||||
| { type: 'done.invoke.resolveHotkeyByPrefix'; data: InteractionMapItem }
|
||||
| {
|
||||
type: 'error.platform.resolveHotkeyByPrefix'
|
||||
data: string | undefined
|
||||
},
|
||||
},
|
||||
id: 'Interaction Map Actor',
|
||||
initial: 'Listening for interaction',
|
||||
description:
|
||||
'Manages the keymap of actions that can be take with the keyboard, mouse, or combination of the two while using the app.',
|
||||
states: {
|
||||
'Listening for interaction': {
|
||||
on: {
|
||||
'Fire event': {
|
||||
target: 'Resolve hotkey',
|
||||
},
|
||||
},
|
||||
},
|
||||
'Resolve hotkey': {
|
||||
invoke: {
|
||||
id: 'resolveHotkeyByPrefix',
|
||||
onDone: {
|
||||
target: 'Execute keymap event',
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: 'Listening for interaction',
|
||||
actions: {
|
||||
type: 'Add last interaction to sequence',
|
||||
},
|
||||
cond: {
|
||||
type: 'There are prefix matches',
|
||||
},
|
||||
},
|
||||
{
|
||||
target: 'Listening for interaction',
|
||||
actions: {
|
||||
type: 'Clear sequence',
|
||||
},
|
||||
},
|
||||
],
|
||||
src: 'Resolve hotkey by prefix',
|
||||
},
|
||||
},
|
||||
'Execute keymap event': {
|
||||
exit: {
|
||||
type: 'Clear sequence',
|
||||
},
|
||||
invoke: {
|
||||
id: 'executeKeymapAction',
|
||||
onDone: {
|
||||
target: 'Listening for interaction',
|
||||
},
|
||||
onError: {
|
||||
target: 'Listening for interaction',
|
||||
},
|
||||
src: 'Execute keymap action',
|
||||
},
|
||||
},
|
||||
},
|
||||
on: {
|
||||
'Add to interaction map': {
|
||||
target: '#Interaction Map Actor',
|
||||
actions: [
|
||||
{
|
||||
type: 'Add to interactionMap',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'Remove from interaction map': {
|
||||
target: '#Interaction Map Actor',
|
||||
internal: true,
|
||||
actions: [
|
||||
{
|
||||
type: 'Remove from interactionMap',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user