mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
Implement settings menu and plugin localisation
This commit is contained in:
1 parent
a62371bc3b
commit
5253bcb369
11 files changed
+199
-42
No files matched your search
@@ -4,7 +4,7 @@ use crate::store::profiles::{get_device_profiles, get_slot, lock_mutexes, save_p
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tauri::Manager;
|
||||
use tauri::{command, AppHandle, Manager};
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Error {
|
||||
@@ -31,7 +31,7 @@ impl From<anyhow::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn get_devices() -> HashMap<std::string::String, crate::devices::DeviceInfo> {
|
||||
DEVICES.lock().await.clone()
|
||||
}
|
||||
@@ -41,18 +41,18 @@ pub async fn update_devices() {
|
||||
let _ = app.get_window("main").unwrap().emit("devices", DEVICES.lock().await.clone());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn get_categories() -> HashMap<std::string::String, Vec<Action>> {
|
||||
CATEGORIES.lock().await.clone()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_profiles(app: tauri::AppHandle, device: &str) -> Result<Vec<String>, Error> {
|
||||
#[command]
|
||||
pub fn get_profiles(app: AppHandle, device: &str) -> Result<Vec<String>, Error> {
|
||||
Ok(get_device_profiles(device, &app)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_selected_profile(app: tauri::AppHandle, device: String) -> Result<crate::shared::Profile, Error> {
|
||||
#[command]
|
||||
pub async fn get_selected_profile(app: AppHandle, device: String) -> Result<crate::shared::Profile, Error> {
|
||||
let mut device_stores = DEVICE_STORES.lock().await;
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
|
||||
@@ -63,8 +63,8 @@ pub async fn get_selected_profile(app: tauri::AppHandle, device: String) -> Resu
|
||||
}
|
||||
|
||||
#[allow(clippy::flat_map_identity)]
|
||||
#[tauri::command]
|
||||
pub async fn set_selected_profile(app: tauri::AppHandle, device: String, id: String) -> Result<(), Error> {
|
||||
#[command]
|
||||
pub async fn set_selected_profile(app: AppHandle, device: String, id: String) -> Result<(), Error> {
|
||||
let mut device_stores = DEVICE_STORES.lock().await;
|
||||
let devices = DEVICES.lock().await;
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
@@ -92,13 +92,13 @@ pub async fn set_selected_profile(app: tauri::AppHandle, device: String, id: Str
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_profile(app: tauri::AppHandle, device: String, profile: String) {
|
||||
#[command]
|
||||
pub async fn delete_profile(app: AppHandle, device: String, profile: String) {
|
||||
let mut profile_stores = PROFILE_STORES.lock().await;
|
||||
profile_stores.remove_profile(&device, &profile, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn create_instance(action: Action, context: Context) -> Result<Option<Vec<ActionInstance>>, Error> {
|
||||
if !action.controllers.contains(&context.controller) {
|
||||
return Ok(None);
|
||||
@@ -128,7 +128,7 @@ pub async fn create_instance(action: Action, context: Context) -> Result<Option<
|
||||
Ok(Some(slot))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn move_slot(source: Context, destination: Context) -> Result<Option<Vec<ActionInstance>>, Error> {
|
||||
if source.controller != destination.controller {
|
||||
return Ok(None);
|
||||
@@ -166,7 +166,7 @@ pub async fn move_slot(source: Context, destination: Context) -> Result<Option<V
|
||||
Ok(Some(vec))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn clear_slot(context: Context) -> Result<(), Error> {
|
||||
let mut locks = lock_mutexes().await;
|
||||
let slot = get_slot(&context, &mut locks).await?;
|
||||
@@ -181,7 +181,7 @@ pub async fn clear_slot(context: Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[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?;
|
||||
@@ -199,8 +199,8 @@ pub async fn remove_instance(context: ActionContext) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn make_info(app: tauri::AppHandle, plugin: String) -> Result<crate::plugins::info_param::Info, Error> {
|
||||
#[command]
|
||||
pub async fn make_info(app: AppHandle, plugin: String) -> Result<crate::plugins::info_param::Info, Error> {
|
||||
let mut path = app.path_resolver().app_config_dir().unwrap();
|
||||
path.push("plugins");
|
||||
path.push(&plugin);
|
||||
@@ -219,7 +219,7 @@ pub async fn make_info(app: tauri::AppHandle, plugin: String) -> Result<crate::p
|
||||
Ok(crate::plugins::info_param::make_info(plugin, manifest.version).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
pub async fn switch_property_inspector(old: Option<ActionContext>, new: Option<ActionContext>) {
|
||||
if let Some(context) = old {
|
||||
let _ = crate::events::outbound::property_inspector::property_inspector_did_appear(context, "propertyInspectorDidDisappear").await;
|
||||
@@ -229,15 +229,15 @@ pub async fn switch_property_inspector(old: Option<ActionContext>, new: Option<A
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_url(app: tauri::AppHandle, url: String) -> Result<(), Error> {
|
||||
#[command]
|
||||
pub async fn open_url(app: AppHandle, url: String) -> Result<(), Error> {
|
||||
if let Err(error) = tauri::api::shell::open(&app.shell_scope(), url, None) {
|
||||
return Err(anyhow::Error::from(error).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[command]
|
||||
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 {
|
||||
@@ -252,7 +252,7 @@ struct UpdateStateEvent {
|
||||
contents: Vec<ActionInstance>,
|
||||
}
|
||||
|
||||
pub async fn update_state(app: &tauri::AppHandle, context: Context, locks: &mut Locks<'_>) -> Result<(), anyhow::Error> {
|
||||
pub async fn update_state(app: &AppHandle, context: Context, locks: &mut Locks<'_>) -> Result<(), anyhow::Error> {
|
||||
let window = app.get_window("main").unwrap();
|
||||
window.emit(
|
||||
"update_state",
|
||||
@@ -264,8 +264,8 @@ pub async fn update_state(app: &tauri::AppHandle, context: Context, locks: &mut
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_plugin(app: tauri::AppHandle, id: String) -> Result<(), Error> {
|
||||
#[command]
|
||||
pub async fn install_plugin(app: AppHandle, id: String) -> Result<(), Error> {
|
||||
let resp = match reqwest::get(format!("https://plugins.amansprojects.com/rezipped/{id}.zip")).await {
|
||||
Ok(resp) => resp,
|
||||
Err(error) => return Err(anyhow::Error::from(error).into()),
|
||||
@@ -297,8 +297,8 @@ pub struct PluginInfo {
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_plugins(app: tauri::AppHandle) -> Result<Vec<PluginInfo>, Error> {
|
||||
#[command]
|
||||
pub async fn list_plugins(app: AppHandle) -> Result<Vec<PluginInfo>, Error> {
|
||||
let mut plugins = vec![];
|
||||
|
||||
let mut entries = match tokio::fs::read_dir(&app.path_resolver().app_config_dir().unwrap().join("plugins/")).await {
|
||||
@@ -330,8 +330,8 @@ pub async fn list_plugins(app: tauri::AppHandle) -> Result<Vec<PluginInfo>, Erro
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_plugin(app: tauri::AppHandle, id: String) -> Result<(), Error> {
|
||||
#[command]
|
||||
pub async fn remove_plugin(app: AppHandle, id: String) -> Result<(), Error> {
|
||||
let locks = lock_mutexes().await;
|
||||
let all = locks.profile_stores.all_from_plugin(&id);
|
||||
drop(locks);
|
||||
@@ -349,3 +349,50 @@ pub async fn remove_plugin(app: tauri::AppHandle, id: String) -> Result<(), Erro
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn get_settings(app: AppHandle) -> Result<crate::store::Settings, Error> {
|
||||
let store = crate::store::get_settings(app).await;
|
||||
match store {
|
||||
Ok(store) => Ok(store.value),
|
||||
Err(error) => Err(anyhow::Error::from(error).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn set_settings(app: AppHandle, settings: crate::store::Settings) -> Result<(), Error> {
|
||||
let mut store = match crate::store::get_settings(app).await {
|
||||
Ok(store) => store,
|
||||
Err(error) => return Err(anyhow::Error::from(error).into())
|
||||
};
|
||||
store.value = settings;
|
||||
store.save()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn get_localisations(app: AppHandle, locale: &str) -> Result<HashMap<String, serde_json::Value>, Error> {
|
||||
let mut localisations: HashMap<String, serde_json::Value> = HashMap::new();
|
||||
|
||||
let mut entries = match tokio::fs::read_dir(&app.path_resolver().app_config_dir().unwrap().join("plugins/")).await {
|
||||
Ok(entries) => entries,
|
||||
Err(error) => return Err(anyhow::Error::from(error).into()),
|
||||
};
|
||||
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
let path = match entry.metadata().await.unwrap().is_symlink() {
|
||||
true => tokio::fs::read_link(entry.path()).await.unwrap(),
|
||||
false => entry.path(),
|
||||
};
|
||||
let metadata = tokio::fs::metadata(&path).await.unwrap();
|
||||
if metadata.is_dir() {
|
||||
let Ok(locale) = tokio::fs::read(path.join(format!("{locale}.json"))).await else { continue };
|
||||
let Ok(locale): Result<serde_json::Value, _> = serde_json::from_slice(&locale) else {
|
||||
continue;
|
||||
};
|
||||
localisations.insert(path.file_name().unwrap().to_str().unwrap().to_owned(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(localisations)
|
||||
}
|
||||
@@ -43,7 +43,10 @@ async fn main() {
|
||||
frontend::update_image,
|
||||
frontend::install_plugin,
|
||||
frontend::list_plugins,
|
||||
frontend::remove_plugin
|
||||
frontend::remove_plugin,
|
||||
frontend::get_settings,
|
||||
frontend::set_settings,
|
||||
frontend::get_localisations
|
||||
])
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
|
||||
@@ -50,3 +50,20 @@ where
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
language: String,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
language: "en".to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_settings(app_handle: tauri::AppHandle) -> Result<Store<Settings>, anyhow::Error> {
|
||||
Store::new("settings", app_handle.path_resolver().app_config_dir().unwrap(), Settings::default())
|
||||
}
|
||||
@@ -3,13 +3,21 @@
|
||||
|
||||
import ListedAction from "./ListedAction.svelte";
|
||||
|
||||
import { localisations } from "$lib/settings";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
let categories: { [name: string]: Action[] } = {};
|
||||
(async () => categories = await invoke("get_categories"))();
|
||||
|
||||
(async () => {
|
||||
categories = await invoke("get_categories");
|
||||
})();
|
||||
function localiseAction(action: Action, localisations: { [plugin: string]: any } | null): { name: string, tooltip: string } {
|
||||
let { name, tooltip } = { name: action.name, tooltip: action.tooltip };
|
||||
if (localisations && localisations[action.plugin] && localisations[action.plugin][action.uuid]) {
|
||||
let localised = localisations[action.plugin][action.uuid];
|
||||
if (localised.Name) name = localised.Name;
|
||||
if (localised.Tooltip) tooltip = localised.Tooltip;
|
||||
}
|
||||
return { name, tooltip };
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grow mt-1 overflow-auto">
|
||||
@@ -17,7 +25,7 @@
|
||||
<h3 class="text-xl font-semibold"> {name} </h3>
|
||||
{#each actions as action}
|
||||
{#if action.visible_in_action_list}
|
||||
<ListedAction {action} />
|
||||
<ListedAction {action} localisation={localiseAction(action, $localisations)} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { Action } from "$lib/Action";
|
||||
|
||||
export let action: Action;
|
||||
export let localisation: { name: string, tooltip: string };
|
||||
|
||||
function handleDragStart(event: DragEvent) {
|
||||
event.dataTransfer?.setData("action", JSON.stringify(action));
|
||||
@@ -11,10 +12,10 @@
|
||||
<div class="flex flex-row items-center mt-2 mb-2 space-x-2">
|
||||
<img
|
||||
src={"http://localhost:57118" + action.icon}
|
||||
alt={action.tooltip}
|
||||
alt={localisation.tooltip}
|
||||
class="w-12 h-12 rounded-sm"
|
||||
draggable
|
||||
on:dragstart={handleDragStart}
|
||||
/>
|
||||
<span> {action.name} </span>
|
||||
<span> {localisation.name} </span>
|
||||
</div>
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Popup from "./Popup.svelte";
|
||||
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { localisations } from "$lib/settings";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
let showPopup = false;
|
||||
|
||||
@@ -36,12 +37,11 @@
|
||||
let search: string = "";
|
||||
</script>
|
||||
|
||||
<hr class="mt-2 border" />
|
||||
<button
|
||||
class="p-1 mt-2 w-full text-sm text-gray-700 bg-gray-100 border rounded-lg"
|
||||
class="p-1 mt-2 w-1/2 text-sm text-gray-700 bg-gray-100 border rounded-lg"
|
||||
on:click={() => showPopup = true}
|
||||
>
|
||||
Get plugins
|
||||
Plugins
|
||||
</button>
|
||||
|
||||
<Popup show={showPopup}>
|
||||
@@ -54,7 +54,12 @@
|
||||
<div class="flex flex-row items-center m-2 p-2 bg-gray-200 rounded-md">
|
||||
<img src={"http://localhost:57118" + plugin.icon} class="w-24 rounded-md" alt={plugin.name} />
|
||||
<div class="ml-4 mr-2">
|
||||
<p><span class="font-semibold"> {plugin.name} </span>{plugin.version}</p>
|
||||
<p>
|
||||
<span class="font-semibold">
|
||||
{($localisations && $localisations[plugin.id] && $localisations[plugin.id].Name) ? $localisations[plugin.id].Name : plugin.name}
|
||||
</span>
|
||||
{plugin.version}
|
||||
</p>
|
||||
{plugin.author}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { settings } from "$lib/settings";
|
||||
|
||||
import Popup from "./Popup.svelte";
|
||||
import Tooltip from "./Tooltip.svelte";
|
||||
|
||||
let showPopup = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="p-1 ml-2 mt-2 w-1/2 text-sm text-gray-700 bg-gray-100 border rounded-lg"
|
||||
on:click={() => showPopup = true}
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
|
||||
<Popup show={showPopup}>
|
||||
<button class="mr-2 my-1 float-right text-xl" on:click={() => showPopup = false}> ✕ </button>
|
||||
<h2 class="m-2 font-semibold text-xl"> Settings </h2>
|
||||
{#if $settings}
|
||||
<div class="flex flex-row items-center m-2 space-x-2">
|
||||
<span> Language: </span>
|
||||
<select bind:value={$settings.language}>
|
||||
<option value="en"> English </option>
|
||||
<option value="es"> Español </option>
|
||||
<option value="zh_CN"> 中文 </option>
|
||||
<option value="fr"> Français </option>
|
||||
<option value="de"> Deutsch </option>
|
||||
<option value="ja"> 日本語 </option>
|
||||
<option value="ko"> 韓国語 </option>
|
||||
</select>
|
||||
<Tooltip>
|
||||
OpenDeck itself is not yet translated.
|
||||
Changing this setting will translate the text from installed plugins into your language for those that support it.
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</Popup>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
let show = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="inline-block cursor-help"
|
||||
on:mouseenter={() => show = true}
|
||||
on:mouseleave={() => show = false}
|
||||
role="cell" tabindex="-1"
|
||||
>
|
||||
<img src="/question.png" class="w-6" alt="Help" />
|
||||
{#if show}
|
||||
<div class="absolute mt-2 p-2 w-96 text-xs bg-gray-100 border-2 rounded-lg">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
export type Settings = {
|
||||
language: string
|
||||
};
|
||||
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const settings: Writable<Settings | null> = writable(null);
|
||||
(async () => settings.set(await invoke("get_settings")))();
|
||||
export const localisations: Writable<{ [plugin: string]: any } | null> = writable(null);
|
||||
settings.subscribe(async (value) => {
|
||||
if (value) {
|
||||
await invoke("set_settings", { settings: value });
|
||||
localisations.set(await invoke("get_localisations", { locale: value.language }));
|
||||
}
|
||||
});
|
||||
@@ -8,9 +8,10 @@
|
||||
import DeviceSelector from "../components/DeviceSelector.svelte";
|
||||
import DeviceView from "../components/DeviceView.svelte";
|
||||
import MultiActionView from "../components/MultiActionView.svelte";
|
||||
import PluginManager from "../components/PluginManager.svelte";
|
||||
import PluginManager from "../components/PluginManager.svelte";
|
||||
import ProfileSelector from "../components/ProfileSelector.svelte";
|
||||
import PropertyInspectorView from "../components/PropertyInspectorView.svelte";
|
||||
import SettingsView from "../components/SettingsView.svelte";
|
||||
|
||||
let selectedDevice: DeviceInfo;
|
||||
$: _selectedDevice = selectedDevice;
|
||||
@@ -39,5 +40,9 @@
|
||||
{/if}
|
||||
{/if}
|
||||
<ActionList />
|
||||
<PluginManager />
|
||||
<hr class="mt-2 border" />
|
||||
<div class="flex flex-row">
|
||||
<PluginManager />
|
||||
<SettingsView />
|
||||
</div>
|
||||
</div>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Reference in new issue
Block a user