Less churny registration using an object instead of array
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useRef, useMemo, memo } from 'react'
|
||||
import { useRef, useMemo, memo, useCallback } from 'react'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
@ -23,6 +23,7 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
||||
import { InteractionSequence } from 'components/Settings/AllKeybindingsFields'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -253,7 +254,7 @@ export function Toolbar({
|
||||
* It contains a tooltip with the title, description, and links
|
||||
* and a hotkey listener
|
||||
*/
|
||||
const ToolbarItemContents = memo(function ToolbarItemContents({
|
||||
function ToolbarItemContents({
|
||||
itemConfig,
|
||||
configCallbackProps,
|
||||
}: {
|
||||
@ -261,32 +262,35 @@ const ToolbarItemContents = memo(function ToolbarItemContents({
|
||||
configCallbackProps: ToolbarItemCallbackProps
|
||||
}) {
|
||||
const { state: interactionMapState } = useInteractionMapContext()
|
||||
const resolvedSequence =
|
||||
interactionMapState.context.overrides[
|
||||
`${KEYBINDING_CATEGORIES.MODELING}.${itemConfig.id}`
|
||||
] ||
|
||||
(itemConfig.hotkey instanceof Array
|
||||
? itemConfig.hotkey[0]
|
||||
: itemConfig.hotkey) ||
|
||||
''
|
||||
const resolvedSequence = useMemo(
|
||||
() =>
|
||||
interactionMapState.context.overrides[
|
||||
`${KEYBINDING_CATEGORIES.MODELING}.${itemConfig.id}`
|
||||
] ||
|
||||
(itemConfig.hotkey instanceof Array
|
||||
? itemConfig.hotkey[0]
|
||||
: itemConfig.hotkey) ||
|
||||
'',
|
||||
[interactionMapState.context.overrides, itemConfig.id, itemConfig.hotkey]
|
||||
)
|
||||
|
||||
useInteractionMap(
|
||||
[
|
||||
{
|
||||
KEYBINDING_CATEGORIES.MODELING,
|
||||
{
|
||||
[itemConfig.id]: {
|
||||
name: itemConfig.id,
|
||||
title: itemConfig.title,
|
||||
sequence: resolvedSequence,
|
||||
action: () => itemConfig.onClick(configCallbackProps),
|
||||
guard: () =>
|
||||
!(
|
||||
itemConfig.status === 'available' &&
|
||||
!!itemConfig.hotkey &&
|
||||
!itemConfig.disabled &&
|
||||
!itemConfig.disableHotkey
|
||||
),
|
||||
itemConfig.status === 'available' &&
|
||||
!!itemConfig.hotkey &&
|
||||
!itemConfig.disabled &&
|
||||
!itemConfig.disableHotkey,
|
||||
ownerId: KEYBINDING_CATEGORIES.MODELING,
|
||||
},
|
||||
],
|
||||
[configCallbackProps.modelingSend, configCallbackProps.commandBarSend],
|
||||
KEYBINDING_CATEGORIES.MODELING
|
||||
},
|
||||
[itemConfig.disabled, itemConfig.disableHotkey, resolvedSequence]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -310,7 +314,10 @@ const ToolbarItemContents = memo(function ToolbarItemContents({
|
||||
{itemConfig.title}
|
||||
</span>
|
||||
{itemConfig.status === 'available' && resolvedSequence ? (
|
||||
<kbd className="flex-none hotkey">{resolvedSequence}</kbd>
|
||||
<InteractionSequence
|
||||
sequence={resolvedSequence}
|
||||
className="flex-nowrap !gap-1"
|
||||
/>
|
||||
) : itemConfig.status === 'kcl-only' ? (
|
||||
<>
|
||||
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
|
||||
@ -359,4 +366,4 @@ const ToolbarItemContents = memo(function ToolbarItemContents({
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -25,8 +25,9 @@ export const CommandBar = () => {
|
||||
}, [pathname])
|
||||
|
||||
useInteractionMap(
|
||||
[
|
||||
{
|
||||
KEYBINDING_CATEGORIES.COMMAND_BAR,
|
||||
{
|
||||
toggle: {
|
||||
name: 'toggle',
|
||||
title: 'Toggle Command Bar',
|
||||
sequence: 'meta+k g RightButton+shift',
|
||||
@ -38,8 +39,9 @@ export const CommandBar = () => {
|
||||
})
|
||||
},
|
||||
guard: () => true,
|
||||
ownerId: KEYBINDING_CATEGORIES.COMMAND_BAR,
|
||||
},
|
||||
{
|
||||
close: {
|
||||
name: 'close',
|
||||
title: 'Close Command Bar',
|
||||
sequence: 'esc',
|
||||
@ -47,10 +49,10 @@ export const CommandBar = () => {
|
||||
commandBarSend({ type: 'Close' })
|
||||
},
|
||||
guard: () => !commandBarState.matches('Closed'),
|
||||
ownerId: KEYBINDING_CATEGORIES.COMMAND_BAR,
|
||||
},
|
||||
],
|
||||
[commandBarState, commandBarSend],
|
||||
KEYBINDING_CATEGORIES.COMMAND_BAR
|
||||
},
|
||||
[commandBarState, commandBarSend]
|
||||
)
|
||||
|
||||
function stepBack() {
|
||||
|
||||
@ -8,9 +8,12 @@ import {
|
||||
sortKeys,
|
||||
} from 'lib/keyboard'
|
||||
import {
|
||||
InteractionMapItem,
|
||||
MouseButtonName,
|
||||
getSortedInteractionMapSequences,
|
||||
interactionMapMachine,
|
||||
makeOverrideKey,
|
||||
normalizeSequence,
|
||||
} from 'machines/interactionMapMachine'
|
||||
import { createContext, useEffect } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
@ -59,42 +62,41 @@ export function InteractionMapMachineProvider({
|
||||
}),
|
||||
'Add to interactionMap': assign({
|
||||
interactionMap: (context, event) => {
|
||||
// normalize any interaction sequences to be sorted
|
||||
const normalizedInteractions = event.data.map((item) => ({
|
||||
...item,
|
||||
sequence: (
|
||||
context.overrides[makeOverrideKey(item)] || item.sequence
|
||||
const newInteractions: Record<string, InteractionMapItem> =
|
||||
Object.fromEntries(
|
||||
Object.entries(event.data.items).map(([name, item]) => [
|
||||
name,
|
||||
{
|
||||
...item,
|
||||
sequence: normalizeSequence(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 = [
|
||||
const newInteractionMap = {
|
||||
...context.interactionMap,
|
||||
...normalizedInteractions,
|
||||
].sort((a, b) => a.sequence.localeCompare(b.sequence))
|
||||
[event.data.ownerId]: {
|
||||
...context.interactionMap[event.data.ownerId],
|
||||
...newInteractions,
|
||||
},
|
||||
}
|
||||
|
||||
console.log('newInteractionMap', newInteractionMap)
|
||||
// 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
|
||||
),
|
||||
]
|
||||
const newInteractionMap = { ...context.interactionMap }
|
||||
if (event.data instanceof Array) {
|
||||
event.data.forEach((key) => {
|
||||
const [ownerId, itemName] = key.split(INTERACTION_MAP_SEPARATOR)
|
||||
delete newInteractionMap[ownerId][itemName]
|
||||
})
|
||||
} else {
|
||||
delete newInteractionMap[event.data]
|
||||
}
|
||||
return newInteractionMap
|
||||
},
|
||||
}),
|
||||
'Merge into overrides': assign({
|
||||
@ -123,16 +125,15 @@ export function InteractionMapMachineProvider({
|
||||
const searchString =
|
||||
(context.currentSequence ? context.currentSequence + ' ' : '') +
|
||||
resolvedInteraction.asString
|
||||
const sortedInteractions = getSortedInteractionMapSequences(context)
|
||||
|
||||
const matches = context.interactionMap.filter((item) =>
|
||||
(
|
||||
context.overrides[makeOverrideKey(item)] || item.sequence
|
||||
).startsWith(searchString)
|
||||
const matches = sortedInteractions.filter(([sequence]) =>
|
||||
sequence.startsWith(searchString)
|
||||
)
|
||||
|
||||
console.log('matches', {
|
||||
matches,
|
||||
interactionMap: context.interactionMap,
|
||||
sortedInteractions,
|
||||
searchString,
|
||||
overrides: context.overrides,
|
||||
})
|
||||
@ -143,9 +144,7 @@ export function InteractionMapMachineProvider({
|
||||
}
|
||||
|
||||
const exactMatches = matches.filter(
|
||||
(item) =>
|
||||
(context.overrides[makeOverrideKey(item)] || item.sequence) ===
|
||||
searchString
|
||||
([sequence]) => sequence === searchString
|
||||
)
|
||||
console.log('exactMatches', exactMatches)
|
||||
if (!exactMatches.length) {
|
||||
@ -157,7 +156,7 @@ export function InteractionMapMachineProvider({
|
||||
|
||||
// Resolve to just one exact match
|
||||
const availableExactMatches = exactMatches.filter(
|
||||
(item) => !item.guard || item.guard(event.data)
|
||||
([_, item]) => !item.guard || item.guard(event.data)
|
||||
)
|
||||
|
||||
console.log('availableExactMatches', availableExactMatches)
|
||||
@ -166,7 +165,7 @@ export function InteractionMapMachineProvider({
|
||||
} else {
|
||||
// return the last-added, available exact match
|
||||
return Promise.resolve(
|
||||
availableExactMatches[availableExactMatches.length - 1]
|
||||
availableExactMatches[availableExactMatches.length - 1][1]
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@ -67,18 +67,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
[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(() => {
|
||||
return filteredPanes.reduce((acc, pane) => {
|
||||
if (pane.showBadge) {
|
||||
@ -102,6 +90,24 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
[context.store?.openPanes, send]
|
||||
)
|
||||
|
||||
useInteractionMap(
|
||||
KEYBINDING_CATEGORIES.USER_INTERFACE,
|
||||
Object.fromEntries(
|
||||
filteredPanes.map((pane) => [
|
||||
pane.id,
|
||||
{
|
||||
name: pane.id,
|
||||
action: () => togglePane(pane.id),
|
||||
keybinding: pane.keybinding,
|
||||
title: `Toggle ${pane.title} pane`,
|
||||
sequence: pane.keybinding,
|
||||
ownerId: KEYBINDING_CATEGORIES.USER_INTERFACE,
|
||||
},
|
||||
])
|
||||
),
|
||||
[filteredPanes, context.store?.openPanes]
|
||||
)
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
className={`group flex-1 flex flex-col z-10 my-2 pr-1 ${paneOpacity} ${pointerEventsCssClass}`}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ActionIcon } from 'components/ActionIcon'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import decamelize from 'decamelize'
|
||||
import { useInteractionMapContext } from 'hooks/useInteractionMapContext'
|
||||
import { resolveInteractionEvent } from 'lib/keyboard'
|
||||
import {
|
||||
@ -13,11 +13,42 @@ export function AllKeybindingsFields() {
|
||||
return (
|
||||
<div className="relative overflow-y-auto">
|
||||
<div className="flex flex-col gap-4 px-2">
|
||||
{state.context.interactionMap.map((item) => (
|
||||
{Object.entries(state.context.interactionMap).map(
|
||||
([category, categoryItems]) => (
|
||||
<KeybindingSection
|
||||
key={category}
|
||||
category={category}
|
||||
items={categoryItems}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function KeybindingSection({
|
||||
category,
|
||||
items,
|
||||
...props
|
||||
}: HTMLProps<HTMLDivElement> & {
|
||||
category: string
|
||||
items: Record<string, InteractionMapItem>
|
||||
}) {
|
||||
return (
|
||||
<section {...props}>
|
||||
<h2
|
||||
id={`category-${category}`}
|
||||
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
||||
>
|
||||
{decamelize(category, { separator: ' ' })}
|
||||
</h2>
|
||||
<div className="flex flex-col my-2 gap-2">
|
||||
{Object.entries(items).map(([_, item]) => (
|
||||
<KeybindingField key={item.ownerId + '-' + item.name} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@ -116,7 +147,7 @@ function KeybindingField({ item }: { item: InteractionMapItem }) {
|
||||
return isEditing ? (
|
||||
<form
|
||||
key={item.ownerId + '-' + item.name}
|
||||
className="flex gap-2 justify-between items-center"
|
||||
className="group flex gap-2 justify-between items-center"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<h3>{item.title}</h3>
|
||||
@ -134,7 +165,7 @@ function KeybindingField({ item }: { item: InteractionMapItem }) {
|
||||
) : (
|
||||
<div
|
||||
key={item.ownerId + '-' + item.name}
|
||||
className="flex gap-2 justify-between items-center"
|
||||
className="group flex gap-2 justify-between items-center"
|
||||
>
|
||||
<h3>{item.title}</h3>
|
||||
<InteractionSequence
|
||||
@ -145,7 +176,7 @@ function KeybindingField({ item }: { item: InteractionMapItem }) {
|
||||
/>
|
||||
<button
|
||||
ref={submitRef}
|
||||
className="p-0 m-0"
|
||||
className="invisible group-focus:visible group-hover:visible p-0 m-0 [&:not(:hover)]:border-transparent"
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
<CustomIcon name="sketch" className="w-5 h-5" />
|
||||
|
||||
@ -1,40 +1,41 @@
|
||||
import { InteractionMapItem } from 'machines/interactionMapMachine'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useInteractionMapContext } from './useInteractionMapContext'
|
||||
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
||||
|
||||
/**
|
||||
* 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
|
||||
/** An ID for the interaction map set. */
|
||||
ownerId: string,
|
||||
/** A set of iteraction map items to add */
|
||||
items: Record<string, InteractionMapItem>,
|
||||
/** Any dependencies that should invalidate the items */
|
||||
deps: any[]
|
||||
) {
|
||||
const interactionMachine = useInteractionMapContext()
|
||||
const mapIdMemoized = useMemo<string>(
|
||||
() => mapId || crypto.randomUUID(),
|
||||
[mapId]
|
||||
)
|
||||
const interactionMapMemoized = useMemo<InteractionMapItem[]>(
|
||||
() => interactionMap.map((item) => ({ ...item, ownerId: mapIdMemoized })),
|
||||
deps
|
||||
const memoizedItems = useMemo(() => items, deps)
|
||||
const itemKeys = Object.keys(memoizedItems).map(
|
||||
(key) => `${ownerId}${INTERACTION_MAP_SEPARATOR}${key}`
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
interactionMachine.send({
|
||||
type: 'Add to interaction map',
|
||||
data: interactionMapMemoized,
|
||||
data: {
|
||||
ownerId,
|
||||
items: memoizedItems,
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
interactionMachine.send({
|
||||
type: 'Remove from interaction map',
|
||||
data: mapIdMemoized,
|
||||
data: itemKeys,
|
||||
})
|
||||
}
|
||||
}, [interactionMapMemoized, mapIdMemoized])
|
||||
}, [memoizedItems])
|
||||
}
|
||||
|
||||
@ -275,7 +275,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
status: 'available',
|
||||
title: 'Exit sketch',
|
||||
showTitle: true,
|
||||
hotkey: 'Esc',
|
||||
hotkey: 'escape',
|
||||
description: 'Exit the current sketch',
|
||||
links: [],
|
||||
},
|
||||
@ -296,7 +296,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
state.matches('Sketch.Rectangle tool.Awaiting second corner'),
|
||||
title: 'Line',
|
||||
hotkey: (state) =>
|
||||
state.matches('Sketch.Line tool') ? ['Esc', 'L'] : 'L',
|
||||
state.matches('Sketch.Line tool') ? ['escape', 'L'] : 'L',
|
||||
description: 'Start drawing straight lines',
|
||||
links: [],
|
||||
isActive: (state) => state.matches('Sketch.Line tool'),
|
||||
@ -320,7 +320,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
!state.matches('Sketch.Tangential arc to'),
|
||||
title: 'Tangential Arc',
|
||||
hotkey: (state) =>
|
||||
state.matches('Sketch.Tangential arc to') ? ['Esc', 'A'] : 'A',
|
||||
state.matches('Sketch.Tangential arc to') ? ['escape', 'A'] : 'A',
|
||||
description: 'Start drawing an arc tangent to the current segment',
|
||||
links: [],
|
||||
isActive: (state) => state.matches('Sketch.Tangential arc to'),
|
||||
@ -400,7 +400,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
!state.matches('Sketch.Rectangle tool'),
|
||||
title: 'Corner rectangle',
|
||||
hotkey: (state) =>
|
||||
state.matches('Sketch.Rectangle tool') ? ['Esc', 'R'] : 'R',
|
||||
state.matches('Sketch.Rectangle tool') ? ['escape', 'R'] : 'R',
|
||||
description: 'Start drawing a rectangle',
|
||||
links: [],
|
||||
isActive: (state) => state.matches('Sketch.Rectangle tool'),
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { createMachine } from 'xstate'
|
||||
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
||||
import { mapKey, sortKeys } from 'lib/keyboard'
|
||||
import { interactionMapCategories } from 'lib/settings/initialKeybindings'
|
||||
import { ContextFrom, createMachine } from 'xstate'
|
||||
|
||||
export type MouseButtonName = `${'Left' | 'Middle' | 'Right'}Button`
|
||||
|
||||
@ -15,11 +18,18 @@ export function makeOverrideKey(interactionMapItem: InteractionMapItem) {
|
||||
return `${interactionMapItem.ownerId}.${interactionMapItem.name}`
|
||||
}
|
||||
|
||||
export type InteractionMap = {
|
||||
[key: (typeof interactionMapCategories)[number]]: Record<
|
||||
string,
|
||||
InteractionMapItem
|
||||
>
|
||||
}
|
||||
|
||||
export const interactionMapMachine = createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPJZznrNZqKyZgAFVqAgANikKb1CAgOAhITUT1EQbUVRBA9gRsEw1SGdwvHLExkLLCR1yCzVyxPU9UGIGz4FeCi9MvLJpVguV4NGbyRl8fRyx1XCTDBQxNH+MJa10i9GyvXZ9k-I4TiwM4MXnYTkpUVL-iQwwTFwzROvhM1bC3DTVSBXQHFsHVtBsEqGvtcrcRbLkyT5GkktlFqxIVY9fiCzyzXUk1DGwnyEGVfxyxzCxAhsXROu8Ka7SxWanVo4CGL499HnQFbGraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
||||
context: {
|
||||
interactionMap: [] as InteractionMapItem[],
|
||||
overrides: {} as { [key: string]: string },
|
||||
interactionMap: {} as InteractionMap,
|
||||
overrides: {} as Record<string, string>,
|
||||
currentSequence: '' as string,
|
||||
},
|
||||
predictableActionArguments: true,
|
||||
@ -29,11 +39,16 @@ export const interactionMapMachine = createMachine({
|
||||
events: {} as
|
||||
| {
|
||||
type: 'Add to interaction map'
|
||||
data: InteractionMapItem[]
|
||||
data: {
|
||||
ownerId: string
|
||||
items: {
|
||||
[key: string]: InteractionMapItem
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'Remove from interaction map'
|
||||
data: string
|
||||
data: string | string[]
|
||||
}
|
||||
| { type: 'Fire event'; data: MouseEvent | KeyboardEvent }
|
||||
| { type: 'Execute keymap action'; data: InteractionMapItem }
|
||||
@ -129,3 +144,32 @@ export const interactionMapMachine = createMachine({
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export function getSortedInteractionMapSequences(
|
||||
context: ContextFrom<typeof interactionMapMachine>
|
||||
) {
|
||||
return Object.values(context.interactionMap)
|
||||
.flatMap((items) =>
|
||||
Object.entries(items).map(
|
||||
([_, item]) =>
|
||||
[context.overrides[makeOverrideKey(item)] || item.sequence, item] as [
|
||||
string,
|
||||
InteractionMapItem
|
||||
]
|
||||
)
|
||||
)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
}
|
||||
|
||||
export function normalizeSequence(sequence: string) {
|
||||
return sequence
|
||||
.split(' ')
|
||||
.map((step) =>
|
||||
step
|
||||
.split(INTERACTION_MAP_SEPARATOR)
|
||||
.sort(sortKeys)
|
||||
.map(mapKey)
|
||||
.join(INTERACTION_MAP_SEPARATOR)
|
||||
)
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user