mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
Introduce new Context type to identify entire slot
This commit is contained in:
1 parent
4dbafce137
commit
c623ababf9
20 files changed
+332
-209
No files matched your search
@@ -10,7 +10,7 @@ use tokio::sync::Mutex;
|
||||
|
||||
static ELGATO_DEVICES: Lazy<Mutex<HashMap<String, AsyncStreamDeck>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
pub async fn update_image(context: &crate::shared::ActionContext, url: &str) -> Result<(), anyhow::Error> {
|
||||
pub async fn update_image(context: &crate::shared::Context, url: &str) -> Result<(), anyhow::Error> {
|
||||
if let Some(device) = ELGATO_DEVICES.lock().await.get(&context.device) {
|
||||
let data = url.split_once(',').unwrap().1;
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(data)?;
|
||||
@@ -19,7 +19,7 @@ pub async fn update_image(context: &crate::shared::ActionContext, url: &str) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clear_image(context: &crate::shared::ActionContext) -> Result<(), StreamDeckError> {
|
||||
pub async fn clear_image(context: &crate::shared::Context) -> Result<(), StreamDeckError> {
|
||||
if let Some(device) = ELGATO_DEVICES.lock().await.get(&context.device) {
|
||||
device.clear_button_image(context.position).await?;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::devices::DEVICES;
|
||||
use crate::shared::{Action, ActionContext, ActionInstance, CATEGORIES};
|
||||
use crate::store::profiles::{get_device_profiles, DEVICE_STORES, PROFILE_STORES};
|
||||
use crate::shared::{Action, ActionContext, ActionInstance, Context, CATEGORIES};
|
||||
use crate::store::profiles::{get_device_profiles, get_slot, lock_mutexes, save_profile, Locks, DEVICE_STORES, PROFILE_STORES};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -58,17 +58,17 @@ pub async fn set_selected_profile(app: tauri::AppHandle, device: String, id: Str
|
||||
|
||||
if store.value.selected_profile != id {
|
||||
let old_profile = &profile_stores.get_profile_store(devices.get(&device).unwrap(), &store.value.selected_profile, &app)?.value;
|
||||
for slot in old_profile.keys.iter().chain(&old_profile.sliders).flat_map(|x| x) {
|
||||
for slot in old_profile.keys.iter().chain(&old_profile.sliders) {
|
||||
for instance in slot {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, slot.len() > 1).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_profile = &profile_stores.get_profile_store(devices.get(&device).unwrap(), &id, &app)?.value;
|
||||
for slot in new_profile.keys.iter().chain(&new_profile.sliders).flat_map(|x| x) {
|
||||
for slot in new_profile.keys.iter().chain(&new_profile.sliders) {
|
||||
for instance in slot {
|
||||
let _ = crate::events::outbound::will_appear::will_appear(instance).await;
|
||||
let _ = crate::events::outbound::will_appear::will_appear(instance, slot.len() > 1).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,108 +85,84 @@ pub async fn delete_profile(app: tauri::AppHandle, device: String, profile: Stri
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_instance(app: tauri::AppHandle, action: Action, context: ActionContext) -> Result<Option<ActionInstance>, Error> {
|
||||
pub async fn create_instance(action: Action, context: Context) -> Result<Option<Vec<ActionInstance>>, Error> {
|
||||
if !action.controllers.contains(&context.controller) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut locks = lock_mutexes().await;
|
||||
let slot = get_slot(&context, &mut locks).await?;
|
||||
let index = match slot.last() {
|
||||
None => 0,
|
||||
Some(instance) => instance.context.index + 1,
|
||||
};
|
||||
|
||||
let instance = ActionInstance {
|
||||
action: action.clone(),
|
||||
context: context.clone(),
|
||||
context: ActionContext::from_context(context.clone(), index),
|
||||
states: action.states.clone(),
|
||||
current_state: 0,
|
||||
settings: serde_json::Value::Object(serde_json::Map::new()),
|
||||
};
|
||||
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
let store = profile_stores.get_profile_store(DEVICES.lock().await.get(&context.device).unwrap(), &context.profile, &app)?;
|
||||
slot.push(instance.clone());
|
||||
let slot = slot.clone();
|
||||
|
||||
if context.controller == "Encoder" {
|
||||
if let Some(old) = &store.value.sliders[context.position as usize] {
|
||||
for instance in old {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
store.value.sliders[context.position as usize] = Some(vec![instance.clone()]);
|
||||
} else {
|
||||
if let Some(old) = &store.value.keys[context.position as usize] {
|
||||
for instance in old {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
store.value.keys[context.position as usize] = Some(vec![instance.clone()]);
|
||||
}
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&instance, index != 0).await;
|
||||
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&instance).await;
|
||||
|
||||
store.save()?;
|
||||
|
||||
Ok(Some(instance))
|
||||
Ok(Some(slot))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn move_instance(app: tauri::AppHandle, mut instance: ActionInstance, context: ActionContext) -> Result<Option<ActionInstance>, Error> {
|
||||
if !instance.action.controllers.contains(&context.controller) {
|
||||
pub async fn move_slot(source: Context, destination: Context) -> Result<Option<Vec<ActionInstance>>, Error> {
|
||||
if source.controller != destination.controller {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
let store = profile_stores.get_profile_store(DEVICES.lock().await.get(&context.device).unwrap(), &context.profile, &app)?;
|
||||
let mut locks = lock_mutexes().await;
|
||||
let src = get_slot(&source, &mut locks).await?;
|
||||
let multi_action = src.len() > 1;
|
||||
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(&instance).await;
|
||||
if instance.context.controller == "Encoder" {
|
||||
store.value.sliders[instance.context.position as usize] = None;
|
||||
} else {
|
||||
store.value.keys[instance.context.position as usize] = None;
|
||||
let mut vec: Vec<ActionInstance> = vec![];
|
||||
|
||||
for (index, instance) in src.iter_mut().enumerate() {
|
||||
let mut new = instance.clone();
|
||||
new.context = ActionContext::from_context(destination.clone(), index as u16);
|
||||
vec.push(new);
|
||||
}
|
||||
|
||||
if context.controller == "Encoder" {
|
||||
if let Some(old) = &store.value.sliders[context.position as usize] {
|
||||
for instance in old {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
instance.context = context.clone();
|
||||
store.value.sliders[context.position as usize] = Some(vec![instance.clone()]);
|
||||
} else {
|
||||
if let Some(old) = &store.value.keys[context.position as usize] {
|
||||
for instance in old {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
instance.context = context.clone();
|
||||
store.value.keys[context.position as usize] = Some(vec![instance.clone()]);
|
||||
let dst = get_slot(&destination, &mut locks).await?;
|
||||
if !dst.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
*dst = vec.clone();
|
||||
|
||||
let src = get_slot(&source, &mut locks).await?;
|
||||
for old in &*src {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(old, multi_action).await;
|
||||
}
|
||||
*src = vec![];
|
||||
for new in &vec {
|
||||
let _ = crate::events::outbound::will_appear::will_appear(new, multi_action).await;
|
||||
}
|
||||
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&instance).await;
|
||||
save_profile(&destination.device, &mut locks).await?;
|
||||
|
||||
store.save()?;
|
||||
|
||||
Ok(Some(instance))
|
||||
Ok(Some(vec))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn clear_slot(app: tauri::AppHandle, context: ActionContext) -> Result<(), Error> {
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
let store = profile_stores.get_profile_store(DEVICES.lock().await.get(&context.device).unwrap(), &context.profile, &app)?;
|
||||
pub async fn clear_slot(context: Context) -> Result<(), Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let slot = get_slot(&context, &mut locks).await?;
|
||||
|
||||
if context.controller == "Encoder" {
|
||||
if let Some(slot) = &store.value.sliders[context.position as usize] {
|
||||
for instance in slot {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
store.value.sliders[context.position as usize] = None;
|
||||
} else {
|
||||
if let Some(slot) = &store.value.keys[context.position as usize] {
|
||||
for instance in slot {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance).await;
|
||||
}
|
||||
}
|
||||
store.value.keys[context.position as usize] = None;
|
||||
for instance in &*slot {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, slot.len() > 1).await;
|
||||
}
|
||||
|
||||
store.save()?;
|
||||
*slot = vec![];
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -222,16 +198,28 @@ pub async fn switch_property_inspector(old: Option<ActionContext>, new: Option<A
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_image(context: ActionContext, image: String) {
|
||||
pub async fn update_image(context: Context, image: String) {
|
||||
if context.device.starts_with("sd-") {
|
||||
if let Err(error) = crate::devices::elgato::update_image(&context, &image).await {
|
||||
log::warn!("Failed to update device image at context {} with image {}: {}", context, image, error);
|
||||
log::warn!("Failed to update device image: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_state(app: &tauri::AppHandle, instance: &ActionInstance) -> Result<(), tauri::Error> {
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct UpdateStateEvent {
|
||||
context: Context,
|
||||
contents: Vec<ActionInstance>,
|
||||
}
|
||||
|
||||
pub async fn update_state(app: &tauri::AppHandle, context: Context, locks: &mut Locks<'_>) -> Result<(), anyhow::Error> {
|
||||
let window = app.get_window("main").unwrap();
|
||||
window.emit("update_state", instance)?;
|
||||
window.emit(
|
||||
"update_state",
|
||||
UpdateStateEvent {
|
||||
contents: get_slot(&context, locks).await?.clone(),
|
||||
context,
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use crate::store::profiles::{get_instance, lock_mutexes, save_profile};
|
||||
pub async fn set_settings(event: super::ContextAndPayloadEvent<serde_json::Value>, from_property_inspector: bool) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
if let Some(instance) = get_instance(&event.context.device, &event.context.controller, event.context.position, event.context.index, &mut locks).await? {
|
||||
if let Some(instance) = get_instance(&event.context, &mut locks).await? {
|
||||
instance.settings = event.payload;
|
||||
outbound::did_receive_settings(instance, !from_property_inspector).await?;
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
@@ -16,8 +16,8 @@ pub async fn set_settings(event: super::ContextAndPayloadEvent<serde_json::Value
|
||||
pub async fn get_settings(event: super::ContextEvent, from_property_inspector: bool) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
if let Some(instance) = get_instance(&event.context.device, &event.context.controller, event.context.position, event.context.index, &mut locks).await? {
|
||||
outbound::did_receive_settings(&instance, from_property_inspector).await?;
|
||||
if let Some(instance) = get_instance(&event.context, &mut locks).await? {
|
||||
outbound::did_receive_settings(instance, from_property_inspector).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct SetStatePayload {
|
||||
pub async fn set_title(event: ContextAndPayloadEvent<SetTitlePayload>) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
if let Some(instance) = get_instance(&event.context.device, &event.context.controller, event.context.position, event.context.index, &mut locks).await? {
|
||||
if let Some(instance) = get_instance(&event.context, &mut locks).await? {
|
||||
if let Some(state) = event.payload.state {
|
||||
instance.states[state as usize].text = event.payload.title.unwrap_or(instance.action.states[state as usize].text.clone());
|
||||
} else {
|
||||
@@ -33,7 +33,7 @@ pub async fn set_title(event: ContextAndPayloadEvent<SetTitlePayload>) -> Result
|
||||
state.text = event.payload.title.clone().unwrap_or(instance.action.states[index].text.clone());
|
||||
}
|
||||
}
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance).await?;
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), (&instance.context).into(), &mut locks).await?;
|
||||
}
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
|
||||
@@ -43,7 +43,7 @@ pub async fn set_title(event: ContextAndPayloadEvent<SetTitlePayload>) -> Result
|
||||
pub async fn set_image(event: ContextAndPayloadEvent<SetImagePayload>) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
if let Some(instance) = get_instance(&event.context.device, &event.context.controller, event.context.position, event.context.index, &mut locks).await? {
|
||||
if let Some(instance) = get_instance(&event.context, &mut locks).await? {
|
||||
if let Some(state) = event.payload.state {
|
||||
instance.states[state as usize].image = event.payload.image.unwrap_or(instance.action.states[state as usize].image.clone());
|
||||
} else {
|
||||
@@ -51,7 +51,7 @@ pub async fn set_image(event: ContextAndPayloadEvent<SetImagePayload>) -> Result
|
||||
state.image = event.payload.image.clone().unwrap_or(instance.action.states[index].image.clone());
|
||||
}
|
||||
}
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance).await?;
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), (&instance.context).into(), &mut locks).await?;
|
||||
}
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
|
||||
@@ -61,9 +61,9 @@ pub async fn set_image(event: ContextAndPayloadEvent<SetImagePayload>) -> Result
|
||||
pub async fn set_state(event: ContextAndPayloadEvent<SetStatePayload>) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
if let Some(instance) = get_instance(&event.context.device, &event.context.controller, event.context.position, event.context.index, &mut locks).await? {
|
||||
if let Some(instance) = get_instance(&event.context, &mut locks).await? {
|
||||
instance.current_state = event.payload.state;
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance).await?;
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), (&instance.context).into(), &mut locks).await?;
|
||||
}
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ use tokio::net::TcpStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
|
||||
|
||||
static PLUGIN_SOCKETS: Lazy<Mutex<HashMap<String, SplitSink<WebSocketStream<TcpStream>, Message>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static PROPERTY_INSPECTOR_SOCKETS: Lazy<Mutex<HashMap<String, SplitSink<WebSocketStream<TcpStream>, Message>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
type Sockets = Lazy<Mutex<HashMap<String, SplitSink<WebSocketStream<TcpStream>, Message>>>>;
|
||||
static PLUGIN_SOCKETS: Sockets = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static PROPERTY_INSPECTOR_SOCKETS: Sockets = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static PLUGIN_QUEUES: Lazy<Mutex<HashMap<String, Vec<Message>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static PROPERTY_INSPECTOR_QUEUES: Lazy<Mutex<HashMap<String, Vec<Message>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
|
||||
@@ -40,10 +40,15 @@ struct DialPressEvent {
|
||||
|
||||
pub async fn dial_rotate(device: &str, index: u8, ticks: i16) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let instance = match get_instance(device, "Encoder", index, 0, &mut locks).await? {
|
||||
Some(instance) => instance,
|
||||
None => return Ok(()),
|
||||
let profile = locks.device_stores.get_device_store(device, crate::APP_HANDLE.get().unwrap())?.value.selected_profile.clone();
|
||||
let context = ActionContext {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
position: index,
|
||||
index: 0,
|
||||
};
|
||||
let Some(instance) = get_instance(&context, &mut locks).await? else { return Ok(()) };
|
||||
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
@@ -68,10 +73,15 @@ pub async fn dial_rotate(device: &str, index: u8, ticks: i16) -> Result<(), anyh
|
||||
|
||||
pub async fn dial_press(device: &str, event: &'static str, index: u8) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let instance = match get_instance(device, "Encoder", index, 0, &mut locks).await? {
|
||||
Some(instance) => instance,
|
||||
None => return Ok(()),
|
||||
let profile = locks.device_stores.get_device_store(device, crate::APP_HANDLE.get().unwrap())?.value.selected_profile.clone();
|
||||
let context = ActionContext {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
position: index,
|
||||
index: 0,
|
||||
};
|
||||
let Some(instance) = get_instance(&context, &mut locks).await? else { return Ok(()) };
|
||||
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{send_to_plugin, GenericInstancePayload};
|
||||
|
||||
use crate::shared::ActionContext;
|
||||
use crate::shared::{ActionContext, Context};
|
||||
use crate::store::profiles::{get_slot, lock_mutexes, save_profile};
|
||||
|
||||
use serde::Serialize;
|
||||
@@ -16,12 +18,17 @@ struct KeyEvent {
|
||||
|
||||
pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let slot = match get_slot(device, "Keypad", key, &mut locks).await? {
|
||||
Some(slot) => slot,
|
||||
None => return Ok(()),
|
||||
let profile = locks.device_stores.get_device_store(device, crate::APP_HANDLE.get().unwrap())?.value.selected_profile.clone();
|
||||
let context = Context {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
position: key,
|
||||
};
|
||||
let slot = get_slot(&context, &mut locks).await?;
|
||||
|
||||
for instance in slot {
|
||||
if slot.len() == 1 {
|
||||
let instance = &slot[0];
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
@@ -29,10 +36,40 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(&instance),
|
||||
payload: GenericInstancePayload::new(instance, false),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
for instance in slot {
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
event: "keyDown",
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance, true),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
event: "keyUp",
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance, true),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -40,30 +77,36 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
|
||||
pub async fn key_up(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
|
||||
let slot = match get_slot(device, "Keypad", key, &mut locks).await? {
|
||||
Some(slot) => slot,
|
||||
None => return Ok(()),
|
||||
let profile = locks.device_stores.get_device_store(device, crate::APP_HANDLE.get().unwrap())?.value.selected_profile.clone();
|
||||
let context = Context {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
position: key,
|
||||
};
|
||||
|
||||
for instance in slot {
|
||||
instance.current_state = (instance.current_state + 1) % (instance.states.len() as u16);
|
||||
let _ = crate::events::frontend::update_state(crate::APP_HANDLE.get().unwrap(), instance).await;
|
||||
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
event: "keyUp",
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let slot = get_slot(&context, &mut locks).await?;
|
||||
if slot.len() != 1 {
|
||||
return Ok(());
|
||||
}
|
||||
let instance = &mut slot[0];
|
||||
|
||||
instance.current_state = (instance.current_state + 1) % (instance.states.len() as u16);
|
||||
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
event: "keyUp",
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance, false),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
save_profile(device, &mut locks).await?;
|
||||
let _ = crate::events::frontend::update_state(crate::APP_HANDLE.get().unwrap(), context, &mut locks).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -15,15 +15,17 @@ struct Coordinates {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct GenericInstancePayload {
|
||||
settings: serde_json::Value,
|
||||
coordinates: Coordinates,
|
||||
controller: String,
|
||||
state: u16,
|
||||
isInMultiAction: bool,
|
||||
}
|
||||
|
||||
impl GenericInstancePayload {
|
||||
fn new(instance: &crate::shared::ActionInstance) -> Self {
|
||||
fn new(instance: &crate::shared::ActionInstance, multi_action: bool) -> Self {
|
||||
let coordinates = match &instance.context.controller[..] {
|
||||
"Encoder" => Coordinates {
|
||||
row: 0,
|
||||
@@ -40,6 +42,7 @@ impl GenericInstancePayload {
|
||||
coordinates,
|
||||
controller: instance.context.controller.clone(),
|
||||
state: instance.current_state,
|
||||
isInMultiAction: multi_action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ struct PropertyInspectorDidAppearEvent {
|
||||
}
|
||||
|
||||
pub async fn send_to_property_inspector(context: ActionContext, message: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context.device, &context.controller, context.position, context.index, &mut lock_mutexes().await).await? {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context, &mut lock_mutexes().await).await? {
|
||||
super::send_to_property_inspector(
|
||||
&context,
|
||||
&SendToEvent {
|
||||
@@ -37,7 +37,7 @@ pub async fn send_to_property_inspector(context: ActionContext, message: serde_j
|
||||
}
|
||||
|
||||
pub async fn send_to_plugin(context: ActionContext, message: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context.device, &context.controller, context.position, context.index, &mut lock_mutexes().await).await? {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context, &mut lock_mutexes().await).await? {
|
||||
super::send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&SendToEvent {
|
||||
@@ -54,7 +54,7 @@ pub async fn send_to_plugin(context: ActionContext, message: serde_json::Value)
|
||||
}
|
||||
|
||||
pub async fn property_inspector_did_appear(context: ActionContext, event: &'static str) -> Result<(), anyhow::Error> {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context.device, &context.controller, context.position, context.index, &mut lock_mutexes().await).await? {
|
||||
if let Some(instance) = crate::store::profiles::get_instance(&context, &mut lock_mutexes().await).await? {
|
||||
if instance.action.property_inspector.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ struct AppearEvent {
|
||||
payload: GenericInstancePayload,
|
||||
}
|
||||
|
||||
pub async fn will_appear(instance: &ActionInstance) -> Result<(), anyhow::Error> {
|
||||
pub async fn will_appear(instance: &ActionInstance, multi_action: bool) -> Result<(), anyhow::Error> {
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&AppearEvent {
|
||||
@@ -19,7 +19,7 @@ pub async fn will_appear(instance: &ActionInstance) -> Result<(), anyhow::Error>
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance),
|
||||
payload: GenericInstancePayload::new(instance, multi_action),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -27,7 +27,7 @@ pub async fn will_appear(instance: &ActionInstance) -> Result<(), anyhow::Error>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn will_disappear(instance: &ActionInstance) -> Result<(), anyhow::Error> {
|
||||
pub async fn will_disappear(instance: &ActionInstance, multi_action: bool) -> Result<(), anyhow::Error> {
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&AppearEvent {
|
||||
@@ -35,13 +35,13 @@ pub async fn will_disappear(instance: &ActionInstance) -> Result<(), anyhow::Err
|
||||
action: instance.action.uuid.clone(),
|
||||
context: instance.context.clone(),
|
||||
device: instance.context.device.clone(),
|
||||
payload: GenericInstancePayload::new(instance),
|
||||
payload: GenericInstancePayload::new(instance, multi_action),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if instance.context.device.starts_with("sd-") {
|
||||
if let Err(error) = crate::devices::elgato::clear_image(&instance.context).await {
|
||||
if let Err(error) = crate::devices::elgato::clear_image(&(&instance.context).into()).await {
|
||||
log::warn!("Failed to clear device image at context {}: {}", instance.context, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ async fn main() {
|
||||
frontend::get_devices,
|
||||
frontend::get_categories,
|
||||
frontend::create_instance,
|
||||
frontend::move_instance,
|
||||
frontend::move_slot,
|
||||
frontend::clear_slot,
|
||||
frontend::get_profiles,
|
||||
frontend::get_selected_profile,
|
||||
|
||||
+41
-3
@@ -93,7 +93,16 @@ pub struct Action {
|
||||
pub states: Vec<ActionState>,
|
||||
}
|
||||
|
||||
/// Information about the slot an instance is located in.
|
||||
/// Location metadata of a slot.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Context {
|
||||
pub device: String,
|
||||
pub profile: String,
|
||||
pub controller: String,
|
||||
pub position: u8,
|
||||
}
|
||||
|
||||
/// Information about the slot and index an instance is located in.
|
||||
#[derive(Clone, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
|
||||
pub struct ActionContext {
|
||||
pub device: String,
|
||||
@@ -128,6 +137,35 @@ impl std::str::FromStr for ActionContext {
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionContext {
|
||||
pub fn from_context(context: Context, index: u16) -> Self {
|
||||
Self {
|
||||
device: context.device,
|
||||
profile: context.profile,
|
||||
controller: context.controller,
|
||||
position: context.position,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActionContext> for Context {
|
||||
fn from(value: ActionContext) -> Self {
|
||||
Self {
|
||||
device: value.device,
|
||||
profile: value.profile,
|
||||
controller: value.controller,
|
||||
position: value.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ActionContext> for Context {
|
||||
fn from(value: &ActionContext) -> Self {
|
||||
Self::from(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of an action.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ActionInstance {
|
||||
@@ -141,8 +179,8 @@ pub struct ActionInstance {
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Profile {
|
||||
pub id: String,
|
||||
pub keys: Vec<Option<Vec<ActionInstance>>>,
|
||||
pub sliders: Vec<Option<Vec<ActionInstance>>>,
|
||||
pub keys: Vec<Vec<ActionInstance>>,
|
||||
pub sliders: Vec<Vec<ActionInstance>>,
|
||||
}
|
||||
|
||||
/// A map of category names to a list of actions in that category.
|
||||
|
||||
@@ -24,8 +24,8 @@ impl ProfileStores {
|
||||
} else {
|
||||
let default = Profile {
|
||||
id: id.to_owned(),
|
||||
keys: repeat_with(|| None).take((device.rows * device.columns).into()).collect(),
|
||||
sliders: repeat_with(|| None).take(device.sliders.into()).collect(),
|
||||
keys: repeat_with(Vec::new).take((device.rows * device.columns).into()).collect(),
|
||||
sliders: repeat_with(Vec::new).take(device.sliders.into()).collect(),
|
||||
};
|
||||
|
||||
let store = Store::new(&path, app.path_resolver().app_config_dir().unwrap(), default).with_context(|| format!("Failed to create store for profile {}", path))?;
|
||||
@@ -47,7 +47,7 @@ impl ProfileStores {
|
||||
pub fn all_from_plugin(&self, plugin: &str) -> Vec<crate::shared::ActionContext> {
|
||||
let mut all = vec![];
|
||||
for store in self.stores.values() {
|
||||
for slot in store.value.keys.iter().chain(&store.value.sliders).flatten() {
|
||||
for slot in store.value.keys.iter().chain(&store.value.sliders) {
|
||||
for instance in slot {
|
||||
if instance.action.plugin == plugin {
|
||||
all.push(instance.context.clone());
|
||||
@@ -132,26 +132,25 @@ pub async fn lock_mutexes() -> Locks<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_slot<'a>(device: &str, controller: &str, position: u8, locks: &'a mut Locks<'_>) -> Result<Option<&'a mut Vec<crate::shared::ActionInstance>>, anyhow::Error> {
|
||||
let selected_profile = &locks.device_stores.get_device_store(device, crate::APP_HANDLE.get().unwrap())?.value.selected_profile;
|
||||
let device = locks.devices.get(device).unwrap();
|
||||
let store = locks.profile_stores.get_profile_store(device, selected_profile, crate::APP_HANDLE.get().unwrap())?;
|
||||
pub async fn get_slot<'a>(context: &crate::shared::Context, locks: &'a mut Locks<'_>) -> Result<&'a mut Vec<crate::shared::ActionInstance>, anyhow::Error> {
|
||||
let device = locks.devices.get(&context.device).unwrap();
|
||||
let store = locks.profile_stores.get_profile_store(device, &context.profile, crate::APP_HANDLE.get().unwrap())?;
|
||||
let profile = &mut store.value;
|
||||
|
||||
let configured = match controller {
|
||||
"Encoder" => profile.sliders[position as usize].as_mut(),
|
||||
_ => profile.keys[position as usize].as_mut(),
|
||||
let configured = match &context.controller[..] {
|
||||
"Encoder" => &mut profile.sliders[context.position as usize],
|
||||
_ => &mut profile.keys[context.position as usize],
|
||||
};
|
||||
|
||||
Ok(configured)
|
||||
}
|
||||
|
||||
pub async fn get_instance<'a>(device: &str, controller: &str, position: u8, index: u16, locks: &'a mut Locks<'_>) -> Result<Option<&'a mut crate::shared::ActionInstance>, anyhow::Error> {
|
||||
let slot = get_slot(device, controller, position, locks).await?;
|
||||
|
||||
match slot {
|
||||
Some(slot) => Ok(Some(&mut slot[index as usize])),
|
||||
None => Ok(None),
|
||||
pub async fn get_instance<'a>(context: &crate::shared::ActionContext, locks: &'a mut Locks<'_>) -> Result<Option<&'a mut crate::shared::ActionInstance>, anyhow::Error> {
|
||||
let slot = get_slot(&(context.into()), locks).await?;
|
||||
if (context.index as usize) < slot.len() {
|
||||
Ok(Some(&mut slot[context.index as usize]))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,18 +19,21 @@
|
||||
}
|
||||
|
||||
async function handleDrop({ dataTransfer }: DragEvent, controller: string, position: number) {
|
||||
let context = `${device.id}.${profile.id}.${controller}.${position}.0`;
|
||||
let context = { device: device.id, profile: profile.id, controller, position };
|
||||
let array = controller == "Encoder" ? profile.sliders : profile.keys;
|
||||
if (dataTransfer?.getData("action")) {
|
||||
array[position] = [await invoke("create_instance", { context, action: JSON.parse(dataTransfer?.getData("action")) })];
|
||||
array[position] = await invoke("create_instance", { context, action: JSON.parse(dataTransfer?.getData("action")) });
|
||||
profile = profile;
|
||||
} else if (dataTransfer?.getData("controller")) {
|
||||
let oldArray = dataTransfer?.getData("controller") == "Encoder" ? profile.sliders : profile.keys;
|
||||
let oldPosition = parseInt(dataTransfer?.getData("position"));
|
||||
let response: ActionInstance = await invoke("move_instance", { context, instance: (oldArray[oldPosition] ?? [null])[0] });
|
||||
let response: ActionInstance[] = await invoke("move_slot", {
|
||||
source: { device: device.id, profile: profile.id, controller: dataTransfer?.getData("controller"), position: oldPosition },
|
||||
destination: context
|
||||
});
|
||||
if (response) {
|
||||
array[position] = [response];
|
||||
oldArray[oldPosition] = null;
|
||||
array[position] = response;
|
||||
oldArray[oldPosition] = [];
|
||||
profile = profile;
|
||||
}
|
||||
}
|
||||
@@ -125,14 +128,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
const nonNull = <T>(o: T | null): o is T => o != null;
|
||||
$: nonEmptySlots = profile.keys.filter(nonNull).concat(profile.sliders.filter(nonNull));
|
||||
$: nonEmptySlots = profile.keys.filter((v) => v.length > 0).concat(profile.sliders.filter((v) => v.length > 0));
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row">
|
||||
{#each { length: device.sliders } as _, i}
|
||||
<Slider
|
||||
context="{device.id}.{profile.id}.Encoder.{i}.0"
|
||||
context={{ device: device.id, profile: profile.id, controller: "Encoder", position: i }}
|
||||
bind:slot={profile.sliders[i]}
|
||||
on:dragover={handleDragOver}
|
||||
on:drop={(event) => handleDrop(event, "Encoder", i)}
|
||||
@@ -145,7 +147,7 @@
|
||||
<div class="flex flex-row">
|
||||
{#each { length: device.columns } as _, c}
|
||||
<Key
|
||||
context="{device.id}.{profile.id}.Keypad.{(r * device.columns) + c}.0"
|
||||
context={{ device: device.id, profile: profile.id, controller: "Keypad", position: (r * device.columns) + c }}
|
||||
bind:slot={profile.keys[(r * device.columns) + c]}
|
||||
on:dragover={handleDragOver}
|
||||
on:drop={(event) => handleDrop(event, "Keypad", (r * device.columns) + c)}
|
||||
@@ -166,16 +168,18 @@
|
||||
✕
|
||||
</button>
|
||||
{#each nonEmptySlots as slot (slot[0].context)}
|
||||
{#if slot[0].action.property_inspector}
|
||||
<iframe
|
||||
title="Property inspector"
|
||||
class="w-full h-full hidden"
|
||||
class:!block={$inspectedInstance == slot[0].context}
|
||||
src={"http://localhost:57118" + slot[0].action.property_inspector + "|opendeck_property_inspector"}
|
||||
name={slot[0].context}
|
||||
bind:this={iframes[slot[0].context]}
|
||||
on:load={() => iframeOnLoad(slot[0])}
|
||||
/>
|
||||
{/if}
|
||||
{#each slot as instance}
|
||||
{#if instance.action.property_inspector}
|
||||
<iframe
|
||||
title="Property inspector"
|
||||
class="w-full h-full hidden"
|
||||
class:!block={$inspectedInstance == instance.context}
|
||||
src={"http://localhost:57118" + instance.action.property_inspector + "|opendeck_property_inspector"}
|
||||
name={instance.context}
|
||||
bind:this={iframes[instance.context]}
|
||||
on:load={() => iframeOnLoad(instance)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
+42
-16
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ActionInstance } from "$lib/ActionInstance";
|
||||
import type { ActionState } from "$lib/ActionState";
|
||||
import type { Context } from "$lib/Context";
|
||||
|
||||
import { inspectedInstance } from "$lib/propertyInspector";
|
||||
import { getImage, renderImage } from "$lib/rendererHelper";
|
||||
@@ -7,33 +9,51 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
export let context: string;
|
||||
export let slot: ActionInstance[] | null;
|
||||
export let context: Context;
|
||||
export let slot: ActionInstance[];
|
||||
|
||||
$: instance = (slot ?? [null])[0];
|
||||
$: state = instance?.states[instance?.current_state];
|
||||
let state: ActionState | undefined;
|
||||
$: {
|
||||
if (!slot.length) {
|
||||
state = undefined;
|
||||
} else if (slot.length > 1) {
|
||||
// @ts-expect-error
|
||||
state = {
|
||||
image: "/multi-action.png",
|
||||
name: "Multi Action",
|
||||
show: false
|
||||
};
|
||||
} else {
|
||||
state = slot[0].states[slot[0].current_state];
|
||||
}
|
||||
}
|
||||
|
||||
listen("update_state", ({ payload }: { payload: ActionInstance }) => {
|
||||
if (payload.context == context) slot = [payload];
|
||||
listen("update_state", ({ payload }: { payload: { context: Context, contents: ActionInstance[] }}) => {
|
||||
if (JSON.stringify(payload.context) == JSON.stringify(context)) slot = payload.contents;
|
||||
});
|
||||
|
||||
function select() {
|
||||
inspectedInstance.set(context);
|
||||
if (!slot || slot.length == 0) return;
|
||||
if (slot.length > 1) {
|
||||
// TODO
|
||||
} else {
|
||||
inspectedInstance.set(slot[0].context);
|
||||
}
|
||||
}
|
||||
|
||||
async function clear(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
if (event.ctrlKey) return;
|
||||
await invoke("clear_slot", { context });
|
||||
instance = null;
|
||||
if ($inspectedInstance == context) inspectedInstance.set(null);
|
||||
slot.forEach((instance) => { if ($inspectedInstance == instance.context) inspectedInstance.set(null); });
|
||||
slot = [];
|
||||
}
|
||||
|
||||
let showAlert = 0;
|
||||
let showOk = 0;
|
||||
let timeouts: number[] = [];
|
||||
listen("show_alert", ({ payload }: { payload: string }) => {
|
||||
if (payload != context) return;
|
||||
if (slot.length != 1 || payload != slot[0].context) return;
|
||||
timeouts.forEach(clearTimeout);
|
||||
showOk = 0;
|
||||
showAlert = 1;
|
||||
@@ -41,7 +61,7 @@
|
||||
timeouts.push(setTimeout(() => showAlert = 0, 2e3));
|
||||
});
|
||||
listen("show_ok", ({ payload }: { payload: string }) => {
|
||||
if (payload != context) return;
|
||||
if (slot.length != 1 || payload != slot[0].context) return;
|
||||
timeouts.forEach(clearTimeout);
|
||||
showAlert = 0;
|
||||
showOk = 1;
|
||||
@@ -51,9 +71,15 @@
|
||||
|
||||
let image: string;
|
||||
$: {
|
||||
let fallback = instance?.action.states[instance?.current_state].image ?? instance?.action.icon;
|
||||
image = getImage(state?.image, fallback);
|
||||
if (state) renderImage(context, state, fallback, showOk > 0, showAlert > 0);
|
||||
if (slot.length > 1) {
|
||||
image = state?.image!;
|
||||
renderImage(context, state!, null!, false, false, false);
|
||||
} else if (slot.length) {
|
||||
let instance = slot[0];
|
||||
let fallback = instance.action.states[instance.current_state].image ?? instance.action.icon;
|
||||
image = getImage(state?.image, fallback);
|
||||
if (state) renderImage(context, state, fallback, showOk > 0, showAlert > 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -63,12 +89,12 @@
|
||||
draggable on:dragstart
|
||||
role="cell" tabindex="-1"
|
||||
>
|
||||
{#if instance && state}
|
||||
{#if state}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
src={image}
|
||||
class="p-2 w-full rounded-xl"
|
||||
alt={instance.action.tooltip}
|
||||
alt={slot.length == 1 ? slot[0].action.tooltip : "Multi Action"}
|
||||
on:click={select} on:keyup={select}
|
||||
on:contextmenu={clear}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionInstance } from "$lib/ActionInstance";
|
||||
import type { Context } from "$lib/Context";
|
||||
|
||||
import { inspectedInstance } from "$lib/propertyInspector";
|
||||
import { getImage } from "$lib/rendererHelper";
|
||||
@@ -7,33 +8,32 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
export let context: string;
|
||||
export let slot: ActionInstance[] | null;
|
||||
export let context: Context;
|
||||
export let slot: ActionInstance[];
|
||||
|
||||
$: instance = (slot ?? [null])[0];
|
||||
$: state = instance?.states[instance?.current_state];
|
||||
$: state = slot[0]?.states[slot[0]?.current_state];
|
||||
|
||||
listen("update_state", ({ payload }: { payload: ActionInstance }) => {
|
||||
if (payload.context == context) slot = [payload];
|
||||
listen("update_state", ({ payload }: { payload: { context: Context, contents: ActionInstance[] }}) => {
|
||||
if (JSON.stringify(payload.context) == JSON.stringify(context)) slot = payload.contents;
|
||||
});
|
||||
|
||||
function select() {
|
||||
inspectedInstance.set(context);
|
||||
inspectedInstance.set(`${context.device}.${context.profile}.${context.controller}.${context.position}.0`);
|
||||
}
|
||||
|
||||
async function clear(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
if (event.ctrlKey) return;
|
||||
await invoke("clear_slot", { context });
|
||||
instance = null;
|
||||
if ($inspectedInstance == context) inspectedInstance.set(null);
|
||||
slot.forEach((instance) => { if ($inspectedInstance == instance.context) inspectedInstance.set(null); });
|
||||
slot = [];
|
||||
}
|
||||
|
||||
let showAlert = 0;
|
||||
let showOk = 0;
|
||||
let timeouts: number[] = [];
|
||||
listen("show_alert", ({ payload }: { payload: string }) => {
|
||||
if (payload != context) return;
|
||||
if (slot.length != 1 || payload != slot[0].context) return;
|
||||
timeouts.forEach(clearTimeout);
|
||||
showOk = 0;
|
||||
showAlert = 1;
|
||||
@@ -41,7 +41,7 @@
|
||||
timeouts.push(setTimeout(() => showAlert = 0, 2e3));
|
||||
});
|
||||
listen("show_ok", ({ payload }: { payload: string }) => {
|
||||
if (payload != context) return;
|
||||
if (slot.length != 1 || payload != slot[0].context) return;
|
||||
timeouts.forEach(clearTimeout);
|
||||
showAlert = 0;
|
||||
showOk = 1;
|
||||
@@ -50,7 +50,11 @@
|
||||
});
|
||||
|
||||
let image: string;
|
||||
$: image = getImage(state?.image, instance?.action.states[instance?.current_state].image ?? instance?.action.icon);
|
||||
$: {
|
||||
if (slot.length) {
|
||||
image = getImage(state?.image, slot[0].action.states[slot[0].current_state].image ?? slot[0].action.icon);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -59,12 +63,12 @@
|
||||
draggable on:dragstart
|
||||
role="cell" tabindex="-1"
|
||||
>
|
||||
{#if instance && state}
|
||||
{#if state}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
src={image}
|
||||
class="p-2 w-full rounded-xl"
|
||||
alt={instance.action.tooltip}
|
||||
alt={slot.length == 1 ? slot[0].action.tooltip : ""}
|
||||
on:click={select} on:keyup={select}
|
||||
on:contextmenu={clear}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type Context = {
|
||||
device: string,
|
||||
profile: string,
|
||||
controller: string,
|
||||
position: number
|
||||
};
|
||||
+2
-2
@@ -3,6 +3,6 @@ import type { ActionInstance } from "./ActionInstance";
|
||||
export type Profile = {
|
||||
device: string,
|
||||
id: string,
|
||||
keys: (ActionInstance[] | null)[],
|
||||
sliders: (ActionInstance[] | null)[]
|
||||
keys: ActionInstance[][],
|
||||
sliders: ActionInstance[][]
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
import type { ActionState } from "./ActionState";
|
||||
import type { Context } from "./Context";
|
||||
|
||||
export function getImage(image: string | undefined, fallback: string | undefined): string {
|
||||
if (!image) return fallback ? getImage(fallback, undefined) : "/alert.png";
|
||||
@@ -18,7 +19,7 @@ export function getImage(image: string | undefined, fallback: string | undefined
|
||||
return image;
|
||||
}
|
||||
|
||||
export async function renderImage(actionContext: string, state: ActionState, fallback: string | undefined, showOk: boolean, showAlert: boolean) {
|
||||
export async function renderImage(slotContext: Context, state: ActionState, fallback: string | undefined, showOk: boolean, showAlert: boolean, processImage: boolean = true) {
|
||||
// Create canvas
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.width = 144;
|
||||
@@ -29,7 +30,7 @@ export async function renderImage(actionContext: string, state: ActionState, fal
|
||||
// Load image
|
||||
let image = document.createElement("img");
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src = getImage(state.image, fallback);
|
||||
image.src = processImage ? getImage(state.image, fallback) : state.image;
|
||||
if (image.src == undefined) return;
|
||||
await new Promise((resolve) => {
|
||||
image.onload = resolve;
|
||||
@@ -76,6 +77,6 @@ export async function renderImage(actionContext: string, state: ActionState, fal
|
||||
context.drawImage(alertImage, 0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
await invoke("update_image", { context: actionContext, image: canvas.toDataURL("image/jpeg") });
|
||||
await invoke("update_image", { context: slotContext, image: canvas.toDataURL("image/jpeg") });
|
||||
canvas.remove();
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Reference in new issue
Block a user