Skip to content

Implement Context Keys & Conditional Keybinds #3676

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion src/schemas/keyBindingSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const zKeybinding = z.object({
// Note: Currently only used to distinguish between global keybindings
// and litegraph canvas keybindings.
// Do NOT use this field in extensions as it has no effect.
targetElementId: z.string().optional()
targetElementId: z.string().optional(),
condition: z.string().optional()
})

// Infer types from schemas
Expand Down
10 changes: 10 additions & 0 deletions src/services/keybindingService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings'
import { useCommandStore } from '@/stores/commandStore'
import { useContextKeyStore } from '@/stores/contextKeyStore'
import {
KeyComboImpl,
KeybindingImpl,
Expand All @@ -11,6 +12,7 @@ export const useKeybindingService = () => {
const keybindingStore = useKeybindingStore()
const commandStore = useCommandStore()
const settingStore = useSettingStore()
const contextKeyStore = useContextKeyStore()

const keybindHandler = async function (event: KeyboardEvent) {
const keyCombo = KeyComboImpl.fromEvent(event)
Expand All @@ -32,6 +34,14 @@ export const useKeybindingService = () => {

const keybinding = keybindingStore.getKeybinding(keyCombo)
if (keybinding && keybinding.targetElementId !== 'graph-canvas') {
// If condition exists and evaluates to false
// TODO: Complex context key evaluation
if (
keybinding.condition &&
contextKeyStore.evaluateCondition(keybinding.condition) !== true
) {
return
}
// Prevent default browser behavior first, then execute the command
event.preventDefault()
await commandStore.execute(keybinding.commandId)
Expand Down
63 changes: 63 additions & 0 deletions src/stores/contextKeyStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { get, set, unset } from 'lodash'
import { defineStore } from 'pinia'
import { reactive } from 'vue'

import { ContextValue, evaluateExpression } from '@/utils/expressionParserUtil'

export const useContextKeyStore = defineStore('contextKeys', () => {
const contextKeys = reactive<Record<string, ContextValue>>({})

/**
* Get a stored context key by path.
* @param {string} path - The dot-separated path to the context key (e.g., 'a.b.c').
* @returns {ContextValue | undefined} The value of the context key, or undefined if not found.
*/
function getContextKey(path: string): ContextValue | undefined {
return get(contextKeys, path)
}

/**
* Set or update a context key value at a given path.
* @param {string} path - The dot-separated path to the context key (e.g., 'a.b.c').
* @param {ContextValue} value - The value to set for the context key.
*/
function setContextKey(path: string, value: ContextValue) {
set(contextKeys, path, value)
}

/**
* Remove a context key by path.
* @param {string} path - The dot-separated path to the context key to remove (e.g., 'a.b.c').
*/
function removeContextKey(path: string) {
unset(contextKeys, path)
}

/**
* Clear all context keys from the store.
*/
function clearAllContextKeys() {
for (const key in contextKeys) {
delete contextKeys[key]
}
}

/**
* Evaluates a context key expression string using the current context keys.
* Returns false if the expression is invalid or if any referenced key is undefined.
* @param {string} expr - The expression string to evaluate (e.g., "key1 && !key2 || (key3 == 'type2')").
* @returns {boolean} The result of the expression evaluation. Returns false if the expression is invalid.
*/
function evaluateCondition(expr: string): boolean {
return evaluateExpression(expr, getContextKey)
}

return {
contextKeys,
getContextKey,
setContextKey,
removeContextKey,
clearAllContextKeys,
evaluateCondition
}
})
5 changes: 4 additions & 1 deletion src/stores/keybindingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export class KeybindingImpl implements Keybinding {
commandId: string
combo: KeyComboImpl
targetElementId?: string
condition?: string

constructor(obj: Keybinding) {
this.commandId = obj.commandId
this.combo = new KeyComboImpl(obj.combo)
this.targetElementId = obj.targetElementId
this.condition = obj.condition
}

equals(other: unknown): boolean {
Expand All @@ -22,7 +24,8 @@ export class KeybindingImpl implements Keybinding {
return raw instanceof KeybindingImpl
? this.commandId === raw.commandId &&
this.combo.equals(raw.combo) &&
this.targetElementId === raw.targetElementId
this.targetElementId === raw.targetElementId &&
this.condition === raw.condition
: false
}
}
Expand Down
Loading