feat: allow customising the delay in between actions within Multi Actions (#393)

Co-authored-by: nekename <63245705+nekename@users.noreply.github.com>
This commit is contained in:
KeryaneKandnekename authored and GitHub committed 2026-07-27 19:09:06 +01:00
1 parent 0a0072a648
commit 64aaf168b5
7 files changed
+130 -14

No files matched your search

+46 -4
View File
@@ -171,11 +171,25 @@ pub async fn remove_instance(context: ActionContext) -> Result<(), Error> {
*slot = None;
} else {
let children = instance.children.as_mut().unwrap();
for (index, instance) in children.iter().enumerate() {
if instance.context == context {
let _ = crate::events::outbound::will_appear::will_disappear(instance, true).await;
let _ = remove_dir_all(instance_images_dir(&instance.context)).await;
for (index, child) in children.iter().enumerate() {
if child.context == context {
let _ = crate::events::outbound::will_appear::will_disappear(child, true).await;
let _ = remove_dir_all(instance_images_dir(&child.context)).await;
children.remove(index);
if instance.action.uuid == "opendeck.multiaction"
&& let Some(settings) = instance.settings.as_object_mut()
&& let Some(delays) = settings.get_mut("delays").and_then(|v| v.as_array_mut())
{
if index == 0 {
if !delays.is_empty() {
delays.remove(0);
}
} else if index - 1 < delays.len() {
delays.remove(index - 1);
}
}
break;
}
}
@@ -224,6 +238,34 @@ pub async fn set_state(context: ActionContext, index: u16, state: ActionState) -
Ok(())
}
#[command]
pub async fn set_child_delay(parent_context: ActionContext, index: usize, delay_ms: u64) -> Result<serde_json::Value, Error> {
let mut locks = acquire_locks_mut().await;
let Some(parent) = get_instance_mut(&parent_context, &mut locks).await? else {
return Ok(serde_json::Value::Null);
};
let delays = parent.settings.get_mut("delays").and_then(|v| v.as_array_mut());
if let Some(arr) = delays {
if arr.len() <= index {
arr.resize(index + 1, serde_json::json!(100));
}
arr[index] = serde_json::json!(delay_ms);
} else {
if !parent.settings.is_object() {
parent.settings = serde_json::Value::Object(serde_json::Map::new());
}
let map = parent.settings.as_object_mut().unwrap();
let mut arr = vec![serde_json::json!(100); index + 1];
arr[index] = serde_json::json!(delay_ms);
map.insert("delays".to_string(), serde_json::Value::Array(arr));
}
let parent_settings = parent.settings.clone();
save_profile_now(&parent_context.device, &mut locks).await?;
Ok(parent_settings)
}
#[command]
pub async fn update_image(context: Context, image: Option<String>) {
if Some(&context.profile) != crate::store::profiles::DEVICE_STORES.write().await.get_selected_profile(&context.device).ok().as_ref() {
+29 -9
View File
@@ -36,7 +36,17 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
let Some(instance) = get_slot_mut(&context, &mut locks).await? else { return Ok(()) };
if instance.action.uuid == "opendeck.multiaction" {
for child in instance.children.as_mut().unwrap() {
let children = instance.children.clone().unwrap_or_default();
let delays: Vec<u64> = instance
.settings
.get("delays")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_u64()).collect())
.unwrap_or_default();
drop(locks);
for (i, child) in children.iter().enumerate() {
send_to_plugin(
&child.action.plugin,
&KeyEvent {
@@ -51,10 +61,6 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
tokio::time::sleep(Duration::from_millis(100)).await;
if child.states.len() == 2 && !child.action.disable_automatic_states {
child.current_state = (child.current_state + 1) % (child.states.len() as u16);
}
send_to_plugin(
&child.action.plugin,
&KeyEvent {
@@ -67,12 +73,26 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
)
.await?;
tokio::time::sleep(Duration::from_millis(100)).await;
let delay = delays.get(i).copied().unwrap_or(100);
if delay > 0 {
tokio::time::sleep(Duration::from_millis(delay)).await;
}
}
let contexts = instance.children.as_ref().unwrap().iter().map(|x| x.context.clone()).collect::<Vec<_>>();
for child in contexts {
let _ = update_state(crate::APP_HANDLE.get().unwrap(), child, &mut locks).await;
let mut locks = acquire_locks_mut().await;
if let Some(instance) = get_slot_mut(&context, &mut locks).await?
&& let Some(children) = &mut instance.children
{
for child in &mut *children {
if child.states.len() == 2 && !child.action.disable_automatic_states {
child.current_state = (child.current_state + 1) % (child.states.len() as u16);
}
}
for child in children.iter().map(|x| x.context.clone()).collect::<Vec<_>>() {
let _ = update_state(crate::APP_HANDLE.get().unwrap(), child, &mut locks).await;
}
}
mark_profile_stale(device, &mut locks).await?;
+1
View File
@@ -83,6 +83,7 @@ async fn main() {
frontend::instances::move_instance,
frontend::instances::remove_instance,
frontend::instances::set_state,
frontend::instances::set_child_delay,
frontend::instances::update_image,
frontend::instances::trigger_virtual_press,
frontend::profiles::get_profiles,
+48 -1
View File
@@ -24,6 +24,10 @@
$: children = profile.keys[$inspectedParentAction!.position]!.children!;
let parentUuid: string;
$: parentUuid = profile.keys[$inspectedParentAction!.position]!.action.uuid;
let parentContext: string;
$: parentContext = profile.keys[$inspectedParentAction!.position]!.context;
let parentSettings: any;
$: parentSettings = profile.keys[$inspectedParentAction!.position]!.settings;
function handleDragOver(event: DragEvent) {
event.preventDefault();
@@ -58,6 +62,12 @@
children.splice(index, 1);
profile.keys[$inspectedParentAction!.position]!.children = children;
if (index == 0) {
profile.keys[$inspectedParentAction!.position]!.settings.delays?.splice(0, 1);
} else {
profile.keys[$inspectedParentAction!.position]!.settings.delays?.splice(index - 1, 1);
}
if (!refocus) return;
await tick();
@@ -71,6 +81,13 @@
items[targetIndex]?.focus();
}
async function setDelay(index: number, event: Event) {
const target = event.currentTarget as HTMLInputElement;
const val = Math.max(0, parseInt(target.value) || 0);
const settings = await invoke<any>("set_child_delay", { parentContext, index, delayMs: val });
profile.keys[$inspectedParentAction!.position]!.settings = settings;
}
function handleListKeydown(event: KeyboardEvent) {
if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
const list = event.currentTarget as HTMLElement;
@@ -126,7 +143,8 @@
{#each children as instance, index}
<!-- svelte-ignore a11y-no-noninteractive-tabindex a11y-no-noninteractive-element-interactions -->
<div
class="flex flex-row items-center mx-4 my-2 bg-neutral-700 hover:bg-neutral-600 transition-colors border border-neutral-600 rounded-lg focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-blue-500"
class="flex flex-row items-center mx-4 my-1 bg-neutral-700 hover:bg-neutral-600 transition-colors border border-neutral-600 rounded-lg focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-blue-500"
class:my-2={parentUuid == "opendeck.toggleaction"}
on:click|stopPropagation={() => ($inspectedInstance = instance.context)}
on:focus|stopPropagation={() => ($inspectedInstance = instance.context)}
on:keydown={(e) => {
@@ -158,6 +176,23 @@
<Trash size="32" class="text-neutral-400" />
</button>
</div>
{#if parentUuid == "opendeck.multiaction" && index < children.length - 1}
<div class="flex flex-row items-center gap-2 mx-14 my-1 px-3 py-2 bg-neutral-800 border border-dashed border-neutral-600 rounded-lg">
<span class="text-xs text-neutral-400">{$t("parent_action_view.delay.label")}</span>
<input
type="number"
min="0"
max="300000"
step="100"
value={parentSettings?.delays?.[index] ?? 100}
on:input={(e) => setDelay(index, e)}
class="no-spinner w-20 px-1 py-0.5 text-center text-sm text-neutral-300 bg-neutral-900 border border-neutral-600 rounded"
aria-label={$t("parent_action_view.delay.aria", { name: children[index + 1].action.name })}
/>
<span class="text-xs text-neutral-500">ms</span>
</div>
{/if}
{/each}
<!-- svelte-ignore a11y-no-noninteractive-tabindex a11y-no-noninteractive-element-interactions -->
<div
@@ -177,3 +212,15 @@
<p class="ml-4 text-xl text-neutral-400">{$t("parent_action_view.drag_paste")}</p>
</div>
</div>
<style>
:global(.no-spinner::-webkit-outer-spin-button),
:global(.no-spinner::-webkit-inner-spin-button) {
-webkit-appearance: none;
margin: 0;
}
:global(.no-spinner[type="number"]) {
-moz-appearance: textfield;
appearance: textfield;
}
</style>
+2
View File
@@ -41,6 +41,8 @@
"no_devices_detected.title": "Keine Geräte gefunden",
"parent_action_view.child": "Unteraktion",
"parent_action_view.children": "Unteraktionen",
"parent_action_view.delay.aria": "Verzögerung für {{name}}",
"parent_action_view.delay.label": "Verzögerung:",
"parent_action_view.drag_copy": "Ziehe eine neue Aktion hierher oder kopiere eine mit Strg+C und füge sie mit Strg+V ein.",
"parent_action_view.drag_paste": "Ziehe oder füge Aktionen hier ein",
"parent_action_view.multi": "Multiaktionen",
+2
View File
@@ -41,6 +41,8 @@
"no_devices_detected.title": "No devices detected",
"parent_action_view.child": "child",
"parent_action_view.children": "children",
"parent_action_view.delay.aria": "Delay for {{name}}",
"parent_action_view.delay.label": "Delay:",
"parent_action_view.drag_copy": "Drag a new action here or copy one with Control+C and paste with Control+V.",
"parent_action_view.drag_paste": "Drop or paste actions here",
"parent_action_view.multi": "Multi Action",
+2
View File
@@ -41,6 +41,8 @@
"no_devices_detected.title": "Inga enheter hittades",
"parent_action_view.child": "underåtgärd",
"parent_action_view.children": "underåtgärder",
"parent_action_view.delay.aria": "Fördröjning för {{name}}",
"parent_action_view.delay.label": "Fördröjning:",
"parent_action_view.drag_copy": "Dra en ny åtgärd hit eller kopiera en med Control+C och klistra in med Control+V.",
"parent_action_view.drag_paste": "Släpp eller klistra in åtgärder här",
"parent_action_view.multi": "Flera åtgärder",