Implement settings menu and plugin localisation

This commit is contained in:
ninjadev64 committed 2024-05-02 19:12:14 +01:00
1 parent a62371bc3b
commit 5253bcb369
11 files changed
+199 -42

No files matched your search

+75 -28
View File
@@ -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)
}
+4 -1
View File
@@ -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()
+17
View File
@@ -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())
}
+12 -4
View File
@@ -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}
+3 -2
View File
@@ -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>
+10 -5
View File
@@ -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>
+38
View File
@@ -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>
+17
View File
@@ -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>
+16
View File
@@ -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 }));
}
});
+7 -2
View File
@@ -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