mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
Implement UI for managing Multi Actions
This commit is contained in:
1 parent
c623ababf9
commit
71049a4804
17 files changed
+282
-147
No files matched your search
@@ -167,6 +167,24 @@ pub async fn clear_slot(context: Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_instance(context: ActionContext) -> Result<(), Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let slot = get_slot(&(&context).into(), &mut locks).await?;
|
||||
|
||||
for (index, instance) in slot.iter().enumerate() {
|
||||
if instance.context == context {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, slot.len() > 1).await;
|
||||
slot.remove(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn make_info(app: tauri::AppHandle, plugin: String) -> Result<crate::plugins::info_param::Info, Error> {
|
||||
let mut path = app.path_resolver().app_config_dir().unwrap();
|
||||
|
||||
@@ -44,7 +44,7 @@ pub async fn dial_rotate(device: &str, index: u8, ticks: i16) -> Result<(), anyh
|
||||
let context = ActionContext {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
controller: "Encoder".to_owned(),
|
||||
position: index,
|
||||
index: 0,
|
||||
};
|
||||
@@ -77,7 +77,7 @@ pub async fn dial_press(device: &str, event: &'static str, index: u8) -> Result<
|
||||
let context = ActionContext {
|
||||
device: device.to_owned(),
|
||||
profile,
|
||||
controller: "Keypad".to_owned(),
|
||||
controller: "Encoder".to_owned(),
|
||||
position: index,
|
||||
index: 0,
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
instance.current_state = (instance.current_state + 1) % (instance.states.len() as u16);
|
||||
send_to_plugin(
|
||||
&instance.action.plugin,
|
||||
&KeyEvent {
|
||||
@@ -70,6 +71,9 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
save_profile(device, &mut locks).await?;
|
||||
let _ = crate::events::frontend::update_state(crate::APP_HANDLE.get().unwrap(), context, &mut locks).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -32,6 +32,7 @@ async fn main() {
|
||||
frontend::create_instance,
|
||||
frontend::move_slot,
|
||||
frontend::clear_slot,
|
||||
frontend::remove_instance,
|
||||
frontend::get_profiles,
|
||||
frontend::get_selected_profile,
|
||||
frontend::set_selected_profile,
|
||||
|
||||
@@ -94,7 +94,7 @@ pub struct Action {
|
||||
}
|
||||
|
||||
/// Location metadata of a slot.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Context {
|
||||
pub device: String,
|
||||
pub profile: String,
|
||||
@@ -103,7 +103,7 @@ pub struct Context {
|
||||
}
|
||||
|
||||
/// Information about the slot and index an instance is located in.
|
||||
#[derive(Clone, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
|
||||
#[derive(Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
|
||||
pub struct ActionContext {
|
||||
pub device: String,
|
||||
pub profile: String,
|
||||
|
||||
@@ -147,11 +147,12 @@ pub async fn get_slot<'a>(context: &crate::shared::Context, locks: &'a mut Locks
|
||||
|
||||
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)
|
||||
for instance in slot {
|
||||
if instance.context == *context {
|
||||
return Ok(Some(instance));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn save_profile<'a>(device: &str, locks: &'a mut Locks<'_>) -> Result<(), anyhow::Error> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
|
||||
import { inspectedInstance } from "$lib/propertyInspector";
|
||||
|
||||
import Key from "./Key.svelte";
|
||||
import Slider from "./Slider.svelte";
|
||||
|
||||
@@ -43,92 +41,6 @@
|
||||
dataTransfer?.setData("controller", controller);
|
||||
dataTransfer?.setData("position", position.toString());
|
||||
}
|
||||
|
||||
let iframes: { [context: string]: HTMLIFrameElement } = {};
|
||||
let iframeContainer: HTMLDivElement;
|
||||
let iframeClosePopup: HTMLButtonElement;
|
||||
let iframePopupsOpen: string[] = [];
|
||||
|
||||
async function iframeOnLoad(instance: ActionInstance) {
|
||||
const iframe = iframes[instance.context];
|
||||
const split = instance.context.split(".");
|
||||
|
||||
let coordinates: { row: number, column: number };
|
||||
if (split[2] == "Encoder") {
|
||||
coordinates = { row: 0, column: parseInt(split[3]) };
|
||||
} else {
|
||||
coordinates = { row: Math.floor(parseInt(split[3]) / device.rows), column: parseInt(split[3]) % device.columns };
|
||||
}
|
||||
|
||||
if (instance == null || !iframe.src || !iframe.src.startsWith("http://localhost:57118")) return;
|
||||
const info = JSON.stringify(await invoke("make_info", { plugin: instance.action.plugin }));
|
||||
|
||||
iframe?.contentWindow?.postMessage({
|
||||
event: "connect",
|
||||
payload: [
|
||||
57116,
|
||||
instance.context,
|
||||
"registerPropertyInspector",
|
||||
info,
|
||||
JSON.stringify({
|
||||
action: instance.action.uuid,
|
||||
context: instance.context,
|
||||
device: split[0],
|
||||
payload: {
|
||||
settings: instance.settings,
|
||||
coordinates
|
||||
}
|
||||
})
|
||||
]
|
||||
}, "http://localhost:57118");
|
||||
}
|
||||
|
||||
const closePopup = (context: string) => {
|
||||
const iframe = iframes[context];
|
||||
iframe.style.position = "";
|
||||
iframe.style.left = "";
|
||||
iframe.style.top = "";
|
||||
iframe.style.width = "100%";
|
||||
iframe.style.height = "100%";
|
||||
iframe.style.display = $inspectedInstance == context ? "block" : "none";
|
||||
iframe.contentWindow?.postMessage({ event: "windowClosed" }, "http://localhost:57118");
|
||||
|
||||
iframePopupsOpen = iframePopupsOpen.filter((e) => e != context);
|
||||
|
||||
if (iframePopupsOpen.length == 0) {
|
||||
iframeContainer.style.position = "";
|
||||
iframeContainer.style.width = "";
|
||||
iframeContainer.style.height = "";
|
||||
iframeContainer.style.padding = "";
|
||||
|
||||
iframeClosePopup.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", ({ data }) => {
|
||||
if (data.event == "windowOpened") {
|
||||
const iframe = iframes[data.payload];
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.left = "36px";
|
||||
iframe.style.top = "36px";
|
||||
iframe.style.width = "calc(100% - 72px)";
|
||||
iframe.style.height = "calc(100% - 72px)";
|
||||
iframe.style.display = "block";
|
||||
|
||||
iframePopupsOpen.push(data.payload);
|
||||
|
||||
iframeContainer.style.position = "absolute";
|
||||
iframeContainer.style.width = "100%";
|
||||
iframeContainer.style.height = "100%";
|
||||
iframeContainer.style.padding = "36px";
|
||||
|
||||
iframeClosePopup.style.display = "block";
|
||||
} else if (data.event == "windowClosed") {
|
||||
closePopup(data.payload);
|
||||
}
|
||||
});
|
||||
|
||||
$: nonEmptySlots = profile.keys.filter((v) => v.length > 0).concat(profile.sliders.filter((v) => v.length > 0));
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row">
|
||||
@@ -158,28 +70,3 @@
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grow overflow-scroll bg-white border-t" bind:this={iframeContainer}>
|
||||
<button
|
||||
bind:this={iframeClosePopup}
|
||||
on:click={() => closePopup(iframePopupsOpen[iframePopupsOpen.length - 1])}
|
||||
class="absolute top-2 right-2 text-2xl font-bold hidden"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
{#each nonEmptySlots as slot (slot[0].context)}
|
||||
{#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>
|
||||
+18
-11
@@ -3,7 +3,7 @@
|
||||
import type { ActionState } from "$lib/ActionState";
|
||||
import type { Context } from "$lib/Context";
|
||||
|
||||
import { inspectedInstance } from "$lib/propertyInspector";
|
||||
import { inspectedInstance, inspectedMultiAction } from "$lib/propertyInspector";
|
||||
import { getImage, renderImage } from "$lib/rendererHelper";
|
||||
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
@@ -12,6 +12,9 @@
|
||||
export let context: Context;
|
||||
export let slot: ActionInstance[];
|
||||
|
||||
export let active: boolean = true;
|
||||
export let scale: number = 1;
|
||||
|
||||
let state: ActionState | undefined;
|
||||
$: {
|
||||
if (!slot.length) {
|
||||
@@ -29,13 +32,15 @@
|
||||
}
|
||||
|
||||
listen("update_state", ({ payload }: { payload: { context: Context, contents: ActionInstance[] }}) => {
|
||||
if (JSON.stringify(payload.context) == JSON.stringify(context)) slot = payload.contents;
|
||||
if (JSON.stringify(payload.context) == JSON.stringify(context) || payload.contents[0]?.context == slot[0]?.context) {
|
||||
slot = payload.contents;
|
||||
}
|
||||
});
|
||||
|
||||
function select() {
|
||||
if (!slot || slot.length == 0) return;
|
||||
if (slot.length > 1) {
|
||||
// TODO
|
||||
inspectedMultiAction.set(context);
|
||||
} else {
|
||||
inspectedInstance.set(slot[0].context);
|
||||
}
|
||||
@@ -43,9 +48,10 @@
|
||||
|
||||
async function clear(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
if (!active) return;
|
||||
if (event.ctrlKey) return;
|
||||
await invoke("clear_slot", { context });
|
||||
slot.forEach((instance) => { if ($inspectedInstance == instance.context) inspectedInstance.set(null); });
|
||||
if (slot.map((instance) => instance.context).includes($inspectedInstance!)) inspectedInstance.set(null);
|
||||
slot = [];
|
||||
}
|
||||
|
||||
@@ -73,18 +79,19 @@
|
||||
$: {
|
||||
if (slot.length > 1) {
|
||||
image = state?.image!;
|
||||
renderImage(context, state!, null!, false, false, false);
|
||||
if (active) 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);
|
||||
if (active && state) renderImage(context, state, fallback, showOk > 0, showAlert > 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative m-2 w-32 h-32 border-2 rounded-md select-none"
|
||||
class="relative m-2 border-2 rounded-md select-none"
|
||||
style="width: calc(8rem * {scale}); height: calc(8rem * {scale});"
|
||||
on:dragover on:drop
|
||||
draggable on:dragstart
|
||||
role="cell" tabindex="-1"
|
||||
@@ -101,10 +108,10 @@
|
||||
{#if state.show}
|
||||
<div class="absolute flex justify-center w-full h-full top-0 left-0 pointer-events-none">
|
||||
<span
|
||||
style={`
|
||||
font-size: ${state.size}px;
|
||||
color: ${state.colour};
|
||||
`}
|
||||
style="
|
||||
font-size: calc({state.size}px * {scale});
|
||||
color: {state.colour};
|
||||
"
|
||||
class:self-start={state.alignment == "top"}
|
||||
class:self-center={state.alignment == "middle"}
|
||||
class:self-end={state.alignment == "bottom"}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import type { ActionInstance } from "$lib/ActionInstance";
|
||||
import type { Context } from "$lib/Context";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
|
||||
import { inspectedMultiAction } from "$lib/propertyInspector";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
import Key from "./Key.svelte";
|
||||
|
||||
export let profile: Profile;
|
||||
|
||||
let slot: ActionInstance[];
|
||||
$: {
|
||||
if ($inspectedMultiAction?.controller == "Encoder") {
|
||||
slot = profile.sliders[$inspectedMultiAction.position];
|
||||
} else {
|
||||
slot = profile.keys[$inspectedMultiAction!.position];
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleDrop({ dataTransfer }: DragEvent) {
|
||||
if (dataTransfer?.getData("action")) {
|
||||
let response: ActionInstance[] = await invoke("create_instance", { context: $inspectedMultiAction, action: JSON.parse(dataTransfer?.getData("action")) });
|
||||
if (response) slot = response;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeInstance(index: number) {
|
||||
await invoke("remove_instance", { context: slot[index].context });
|
||||
let temp = [...slot];
|
||||
temp.splice(index, 1);
|
||||
slot = temp;
|
||||
}
|
||||
|
||||
let context: Context;
|
||||
context = null!;
|
||||
</script>
|
||||
|
||||
<div class="px-6 pt-6 pb-4">
|
||||
<button class="float-right text-xl" on:click={() => $inspectedMultiAction = null}> ✕ </button>
|
||||
<h1 class="font-semibold text-2xl"> Multi Action </h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col h-80 overflow-scroll">
|
||||
{#each slot as instance, index}
|
||||
<div class="flex flex-row items-center mx-4 my-1 bg-gray-100 rounded-md">
|
||||
<Key slot={[instance]} {context} active={false} scale={3/4} />
|
||||
<p class="ml-4 text-xl"> {instance.action.name} </p>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
src="/rubbish.png"
|
||||
class="ml-auto mr-10 w-8 cursor-pointer"
|
||||
alt="Remove action"
|
||||
on:click={() => removeInstance(index)} on:keyup={() => removeInstance(index)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
<div
|
||||
class="flex flex-row items-center mx-4 mt-1 mb-4 bg-gray-100 border-2 border-dashed rounded-md"
|
||||
on:dragover={handleDragOver} on:drop={handleDrop}
|
||||
role="cell" tabindex="-1"
|
||||
>
|
||||
<img src="/cube.png" class="m-2 w-24 rounded-xl" alt="Add new action" />
|
||||
<p class="ml-4 text-xl text-gray-500"> Drop actions here </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,6 +23,12 @@
|
||||
if (!profiles.includes(id)) profiles = [ ...profiles, id ];
|
||||
}
|
||||
|
||||
async function deleteProfile(id: string) {
|
||||
await invoke("delete_profile", { device: device.id, profile: id });
|
||||
profiles.splice(profiles.indexOf(id), 1);
|
||||
profiles = profiles;
|
||||
}
|
||||
|
||||
let oldValue: string;
|
||||
let value: string;
|
||||
$: {
|
||||
@@ -69,11 +75,14 @@
|
||||
<input type="radio" bind:group={value} value={profile} />
|
||||
{profile}
|
||||
{#if profile != value}
|
||||
<button class="float-right" on:click={() => {
|
||||
invoke("delete_profile", { device: device.id, profile });
|
||||
profiles.splice(i, 1);
|
||||
profiles = profiles;
|
||||
}}> ✕ </button>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
src="/rubbish.png"
|
||||
class="float-right w-6 cursor-pointer"
|
||||
alt="Remove profile"
|
||||
on:click={() => deleteProfile(profile)}
|
||||
on:keyup={() => deleteProfile(profile)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import type { ActionInstance } from "$lib/ActionInstance";
|
||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
|
||||
import { inspectedInstance } from "$lib/propertyInspector";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
let iframes: { [context: string]: HTMLIFrameElement } = {};
|
||||
let iframeContainer: HTMLDivElement;
|
||||
let iframeClosePopup: HTMLButtonElement;
|
||||
let iframePopupsOpen: string[] = [];
|
||||
|
||||
export let device: DeviceInfo;
|
||||
export let profile: Profile;
|
||||
|
||||
async function iframeOnLoad(instance: ActionInstance) {
|
||||
const iframe = iframes[instance.context];
|
||||
const split = instance.context.split(".");
|
||||
|
||||
let coordinates: { row: number, column: number };
|
||||
if (split[2] == "Encoder") {
|
||||
coordinates = { row: 0, column: parseInt(split[3]) };
|
||||
} else {
|
||||
coordinates = { row: Math.floor(parseInt(split[3]) / device.rows), column: parseInt(split[3]) % device.columns };
|
||||
}
|
||||
|
||||
if (instance == null || !iframe.src || !iframe.src.startsWith("http://localhost:57118")) return;
|
||||
const info = JSON.stringify(await invoke("make_info", { plugin: instance.action.plugin }));
|
||||
|
||||
iframe?.contentWindow?.postMessage({
|
||||
event: "connect",
|
||||
payload: [
|
||||
57116,
|
||||
instance.context,
|
||||
"registerPropertyInspector",
|
||||
info,
|
||||
JSON.stringify({
|
||||
action: instance.action.uuid,
|
||||
context: instance.context,
|
||||
device: split[0],
|
||||
payload: {
|
||||
settings: instance.settings,
|
||||
coordinates
|
||||
}
|
||||
})
|
||||
]
|
||||
}, "http://localhost:57118");
|
||||
}
|
||||
|
||||
const closePopup = (context: string) => {
|
||||
const iframe = iframes[context];
|
||||
iframe.style.position = "";
|
||||
iframe.style.left = "";
|
||||
iframe.style.top = "";
|
||||
iframe.style.width = "100%";
|
||||
iframe.style.height = "100%";
|
||||
iframe.style.display = $inspectedInstance == context ? "block" : "none";
|
||||
iframe.contentWindow?.postMessage({ event: "windowClosed" }, "http://localhost:57118");
|
||||
|
||||
iframePopupsOpen = iframePopupsOpen.filter((e) => e != context);
|
||||
|
||||
if (iframePopupsOpen.length == 0) {
|
||||
iframeContainer.style.position = "";
|
||||
iframeContainer.style.width = "";
|
||||
iframeContainer.style.height = "";
|
||||
iframeContainer.style.padding = "";
|
||||
|
||||
iframeClosePopup.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", ({ data }) => {
|
||||
if (data.event == "windowOpened") {
|
||||
const iframe = iframes[data.payload];
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.left = "36px";
|
||||
iframe.style.top = "36px";
|
||||
iframe.style.width = "calc(100% - 72px)";
|
||||
iframe.style.height = "calc(100% - 72px)";
|
||||
iframe.style.display = "block";
|
||||
|
||||
iframePopupsOpen.push(data.payload);
|
||||
|
||||
iframeContainer.style.position = "absolute";
|
||||
iframeContainer.style.width = "100%";
|
||||
iframeContainer.style.height = "100%";
|
||||
iframeContainer.style.padding = "36px";
|
||||
|
||||
iframeClosePopup.style.display = "block";
|
||||
} else if (data.event == "windowClosed") {
|
||||
closePopup(data.payload);
|
||||
}
|
||||
});
|
||||
|
||||
$: instances = profile.keys.reduce((prev, current) => prev.concat(current), []).concat(profile.sliders.reduce((prev, current) => prev.concat(current), []));
|
||||
</script>
|
||||
|
||||
<div class="grow overflow-scroll bg-white border-t" bind:this={iframeContainer}>
|
||||
<button
|
||||
bind:this={iframeClosePopup}
|
||||
on:click={() => closePopup(iframePopupsOpen[iframePopupsOpen.length - 1])}
|
||||
class="absolute top-2 right-2 text-2xl font-bold hidden"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
{#each instances 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}
|
||||
</div>
|
||||
@@ -25,7 +25,7 @@
|
||||
event.preventDefault();
|
||||
if (event.ctrlKey) return;
|
||||
await invoke("clear_slot", { context });
|
||||
slot.forEach((instance) => { if ($inspectedInstance == instance.context) inspectedInstance.set(null); });
|
||||
if ($inspectedInstance == slot[0]?.context) inspectedInstance.set(null);
|
||||
slot = [];
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@
|
||||
{#if state.show}
|
||||
<div class="absolute flex justify-center w-full aspect-square top-[50%] -translate-y-1/2 left-0 pointer-events-none">
|
||||
<span
|
||||
style={`
|
||||
font-size: ${state.size}px;
|
||||
color: ${state.colour};
|
||||
style="
|
||||
font-size: {state.size}px;
|
||||
color: {state.colour};
|
||||
scale: 0.5;
|
||||
`}
|
||||
"
|
||||
class:self-start={state.alignment == "top"}
|
||||
class:self-center={state.alignment == "middle"}
|
||||
class:self-end={state.alignment == "bottom"}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Context } from "./Context";
|
||||
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const inspectedInstance: Writable<string | null> = writable(null);
|
||||
@@ -8,3 +10,5 @@ inspectedInstance.subscribe(async (value) => {
|
||||
await invoke("switch_property_inspector", { old, new: value });
|
||||
old = value;
|
||||
});
|
||||
|
||||
export const inspectedMultiAction: Writable<Context | null> = writable(null);
|
||||
+16
-4
@@ -2,10 +2,14 @@
|
||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||
import type { Profile } from "$lib/Profile";
|
||||
|
||||
import { inspectedMultiAction } from "$lib/propertyInspector";
|
||||
|
||||
import ActionList from "../components/ActionList.svelte";
|
||||
import DeviceSelector from "../components/DeviceSelector.svelte";
|
||||
import DeviceView from "../components/DeviceView.svelte";
|
||||
import MultiActionView from "../components/MultiActionView.svelte";
|
||||
import ProfileSelector from "../components/ProfileSelector.svelte";
|
||||
import PropertyInspectorView from "../components/PropertyInspectorView.svelte";
|
||||
|
||||
let selectedDevice: DeviceInfo;
|
||||
$: _selectedDevice = selectedDevice;
|
||||
@@ -15,15 +19,23 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col grow">
|
||||
{#if selectedDevice && selectedProfile}
|
||||
{#if $inspectedMultiAction}
|
||||
<MultiActionView bind:profile={_selectedProfile} />
|
||||
{:else if selectedDevice && selectedProfile}
|
||||
<DeviceView bind:device={_selectedDevice} bind:profile={_selectedProfile} />
|
||||
{/if}
|
||||
|
||||
{#if selectedProfile}
|
||||
<PropertyInspectorView bind:device={_selectedDevice} bind:profile={_selectedProfile} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col p-2 grow max-w-[18rem] h-full border-l">
|
||||
<DeviceSelector bind:device={selectedDevice} />
|
||||
{#if selectedDevice}
|
||||
<ProfileSelector bind:device={_selectedDevice} bind:profile={selectedProfile} />
|
||||
{#if !$inspectedMultiAction}
|
||||
<DeviceSelector bind:device={selectedDevice} />
|
||||
{#if selectedDevice}
|
||||
<ProfileSelector bind:device={_selectedDevice} bind:profile={selectedProfile} />
|
||||
{/if}
|
||||
{/if}
|
||||
<ActionList />
|
||||
</div>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Reference in new issue
Block a user