mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-20 11:35:48 +00:00
feat: can update plugins; show plugins that are installed but no longer available
This commit is contained in:
parent
01a61bc842
commit
e8922203e8
6 changed files with 115 additions and 49 deletions
|
@ -20,8 +20,7 @@ use std::{
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PluginConfig {
|
||||
pub appid: String,
|
||||
pub version: String,
|
||||
pub plugin: Plugin,
|
||||
pub enabled: bool,
|
||||
pub exec_path: PathBuf,
|
||||
}
|
||||
|
@ -29,14 +28,19 @@ pub struct PluginConfig {
|
|||
impl From<&Plugin> for PluginConfig {
|
||||
fn from(p: &Plugin) -> Self {
|
||||
Self {
|
||||
appid: p.appid.clone(),
|
||||
version: p.version.clone(),
|
||||
plugin: p.clone(),
|
||||
enabled: true,
|
||||
exec_path: p.exec_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PluginConfig> for Plugin {
|
||||
fn from(cp: &PluginConfig) -> Self {
|
||||
cp.plugin.clone()
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_WIN_SIZE: [i32; 2] = [360, 400];
|
||||
|
||||
const fn default_win_size() -> [i32; 2] {
|
||||
|
|
|
@ -268,7 +268,10 @@ impl App {
|
|||
.filter_map(|cp| {
|
||||
if cp.enabled {
|
||||
if let Err(e) = mark_as_executable(&cp.exec_path) {
|
||||
eprintln!("Failed to mark plugin {} as executable: {e}", cp.appid);
|
||||
eprintln!(
|
||||
"Failed to mark plugin {} as executable: {e}",
|
||||
cp.plugin.appid
|
||||
);
|
||||
None
|
||||
} else {
|
||||
Some(format!("'{}'", cp.exec_path.to_string_lossy()))
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{paths::get_plugins_dir, util::file_utils::mark_as_executable};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Plugin {
|
||||
pub appid: String,
|
||||
pub name: String,
|
||||
|
@ -19,20 +19,6 @@ pub struct Plugin {
|
|||
pub exec_url: String,
|
||||
}
|
||||
|
||||
impl PartialEq for Plugin {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.appid == other.appid
|
||||
&& self.name == other.name
|
||||
&& self.version == other.version
|
||||
&& self.icon_url == other.icon_url
|
||||
&& self.short_description == other.short_description
|
||||
&& self.description == other.description
|
||||
&& self.hompage_url == other.hompage_url
|
||||
&& self.screenshots == other.screenshots
|
||||
&& self.exec_url == other.exec_url
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn exec_path(&self) -> PathBuf {
|
||||
get_plugins_dir().join(format!("{}.AppImage", self.appid))
|
||||
|
|
|
@ -172,7 +172,7 @@ impl AsyncComponent for PluginStore {
|
|||
Self::Input::Refresh => {
|
||||
self.set_refreshing(true);
|
||||
// TODO: populate from web
|
||||
self.set_plugins(vec![
|
||||
let mut plugins = vec![
|
||||
Plugin {
|
||||
appid: "com.github.galiser.wlx-overlay-s".into(),
|
||||
name: "WLX Overlay S".into(),
|
||||
|
@ -186,7 +186,22 @@ impl AsyncComponent for PluginStore {
|
|||
short_description: Some("Access your Wayland/X11 desktop".into()),
|
||||
exec_url: "https://github.com/galister/wlx-overlay-s/releases/download/v0.4.4/WlxOverlay-S-v0.4.4-x86_64.AppImage".into()
|
||||
},
|
||||
]);
|
||||
];
|
||||
{
|
||||
let appids_from_web = plugins
|
||||
.iter()
|
||||
.map(|p| p.appid.clone())
|
||||
.collect::<Vec<String>>();
|
||||
// add all plugins that are in config but not retrieved
|
||||
plugins.extend(self.config_plugins.values().filter_map(|cp| {
|
||||
if appids_from_web.contains(&cp.plugin.appid) {
|
||||
None
|
||||
} else {
|
||||
Some(Plugin::from(cp))
|
||||
}
|
||||
}));
|
||||
}
|
||||
self.set_plugins(plugins);
|
||||
{
|
||||
let mut guard = self.plugin_rows.as_mut().unwrap().guard();
|
||||
guard.clear();
|
||||
|
@ -197,6 +212,10 @@ impl AsyncComponent for PluginStore {
|
|||
.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.enabled),
|
||||
needs_update: self
|
||||
.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.plugin.version != plugin.version),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -256,8 +275,8 @@ impl AsyncComponent for PluginStore {
|
|||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(true));
|
||||
self.details.emit(StoreDetailMsg::Refresh(true));
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(true, false));
|
||||
self.details.emit(StoreDetailMsg::Refresh(true, false));
|
||||
self.set_locked(false);
|
||||
}
|
||||
Self::Input::Remove(plugin, row_sender) => {
|
||||
|
@ -282,8 +301,8 @@ impl AsyncComponent for PluginStore {
|
|||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
}
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(false));
|
||||
self.details.emit(StoreDetailMsg::Refresh(false));
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(false, false));
|
||||
self.details.emit(StoreDetailMsg::Refresh(false, false));
|
||||
self.set_locked(false);
|
||||
}
|
||||
Self::Input::SetEnabled(plugin, enabled) => {
|
||||
|
@ -298,11 +317,17 @@ impl AsyncComponent for PluginStore {
|
|||
.find(|row| row.is_some_and(|row| row.plugin.appid == plugin.appid))
|
||||
.flatten()
|
||||
{
|
||||
row.input_sender.emit(StoreRowModelMsg::Refresh(enabled));
|
||||
row.input_sender.emit(StoreRowModelMsg::Refresh(
|
||||
enabled,
|
||||
cp.plugin.version != plugin.version,
|
||||
));
|
||||
} else {
|
||||
eprintln!("could not find corresponding listbox row!")
|
||||
}
|
||||
self.details.emit(StoreDetailMsg::Refresh(enabled));
|
||||
self.details.emit(StoreDetailMsg::Refresh(
|
||||
enabled,
|
||||
cp.plugin.version != plugin.version,
|
||||
));
|
||||
} else {
|
||||
eprintln!(
|
||||
"failed to set plugin {} enabled: could not find in hashmap",
|
||||
|
@ -324,6 +349,9 @@ impl AsyncComponent for PluginStore {
|
|||
self.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.enabled),
|
||||
self.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.plugin.version != plugin.version),
|
||||
));
|
||||
self.main_stack
|
||||
.as_ref()
|
||||
|
|
|
@ -11,15 +11,16 @@ pub struct StoreDetail {
|
|||
carousel: Option<adw::Carousel>,
|
||||
#[tracker::do_not_track]
|
||||
icon: Option<gtk::Image>,
|
||||
needs_update: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum StoreDetailMsg {
|
||||
SetPlugin(Plugin, bool),
|
||||
SetPlugin(Plugin, bool, bool),
|
||||
SetIcon,
|
||||
SetScreenshots,
|
||||
Refresh(bool),
|
||||
Refresh(bool, bool),
|
||||
Install,
|
||||
Remove,
|
||||
SetEnabled(bool),
|
||||
|
@ -130,6 +131,20 @@ impl AsyncComponent for StoreDetail {
|
|||
sender.input(Self::Input::Remove);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
#[track = "model.changed(Self::plugin()) || model.changed(Self::needs_update())"]
|
||||
set_visible: model
|
||||
.plugin
|
||||
.as_ref()
|
||||
.is_some_and(|p| p.is_installed()) && model.needs_update,
|
||||
add_css_class: "suggested-action",
|
||||
set_label: "Update",
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::Install);
|
||||
}
|
||||
},
|
||||
gtk::Switch {
|
||||
#[track = "model.changed(Self::plugin())"]
|
||||
set_visible: model.plugin.as_ref()
|
||||
|
@ -194,9 +209,10 @@ impl AsyncComponent for StoreDetail {
|
|||
self.reset();
|
||||
|
||||
match message {
|
||||
Self::Input::SetPlugin(p, enabled) => {
|
||||
Self::Input::SetPlugin(p, enabled, needs_update) => {
|
||||
self.set_plugin(Some(p));
|
||||
self.set_enabled(enabled);
|
||||
self.set_needs_update(needs_update);
|
||||
sender.input(Self::Input::SetIcon);
|
||||
sender.input(Self::Input::SetScreenshots);
|
||||
}
|
||||
|
@ -244,9 +260,10 @@ impl AsyncComponent for StoreDetail {
|
|||
}
|
||||
}
|
||||
}
|
||||
Self::Input::Refresh(enabled) => {
|
||||
Self::Input::Refresh(enabled, needs_update) => {
|
||||
self.mark_all_changed();
|
||||
self.set_enabled(enabled);
|
||||
self.set_needs_update(needs_update);
|
||||
}
|
||||
Self::Input::Install => {
|
||||
if let Some(plugin) = self.plugin.as_ref() {
|
||||
|
@ -284,6 +301,7 @@ impl AsyncComponent for StoreDetail {
|
|||
enabled: false,
|
||||
carousel: None,
|
||||
icon: None,
|
||||
needs_update: false,
|
||||
};
|
||||
let widgets = view_output!();
|
||||
|
||||
|
|
|
@ -15,18 +15,20 @@ pub struct StoreRowModel {
|
|||
#[tracker::do_not_track]
|
||||
pub input_sender: relm4::Sender<StoreRowModelMsg>,
|
||||
pub enabled: bool,
|
||||
pub needs_update: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StoreRowModelInit {
|
||||
pub plugin: Plugin,
|
||||
pub enabled: bool,
|
||||
pub needs_update: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StoreRowModelMsg {
|
||||
LoadIcon,
|
||||
Refresh(bool),
|
||||
Refresh(bool, bool),
|
||||
SetEnabled(bool),
|
||||
}
|
||||
|
||||
|
@ -105,22 +107,45 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
#[track = "self.changed(StoreRowModel::plugin())"]
|
||||
set_visible: self.plugin.is_installed(),
|
||||
set_icon_name: "app-remove-symbolic",
|
||||
add_css_class: "destructive-action",
|
||||
set_tooltip_text: Some("Remove"),
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 6,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
connect_clicked[sender, plugin] => move |_| {
|
||||
sender
|
||||
.output(Self::Output::Remove(
|
||||
plugin.clone(),
|
||||
sender.input_sender().clone()
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
gtk::Button {
|
||||
#[track = "self.changed(StoreRowModel::plugin())"]
|
||||
set_visible: self.plugin.is_installed(),
|
||||
set_icon_name: "app-remove-symbolic",
|
||||
add_css_class: "destructive-action",
|
||||
set_tooltip_text: Some("Remove"),
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
connect_clicked[sender, plugin] => move |_| {
|
||||
sender
|
||||
.output(Self::Output::Remove(
|
||||
plugin.clone(),
|
||||
sender.input_sender().clone()
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
#[track = "self.changed(StoreRowModel::plugin()) || self.changed(StoreRowModel::needs_update())"]
|
||||
set_visible: self.plugin.is_installed() && self.needs_update,
|
||||
set_icon_name: "view-refresh-symbolic",
|
||||
add_css_class: "suggested-action",
|
||||
set_tooltip_text: Some("Update"),
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
connect_clicked[sender, plugin] => move |_| {
|
||||
sender
|
||||
.output(Self::Output::Install(
|
||||
plugin.clone(),
|
||||
sender.input_sender().clone()
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
},
|
||||
},
|
||||
gtk::Switch {
|
||||
#[track = "self.changed(StoreRowModel::plugin())"]
|
||||
|
@ -162,9 +187,10 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
.output(Self::Output::SetEnabled(self.plugin.clone(), state))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
Self::Input::Refresh(enabled) => {
|
||||
Self::Input::Refresh(enabled, needs_update) => {
|
||||
self.mark_all_changed();
|
||||
self.set_enabled(enabled)
|
||||
self.set_enabled(enabled);
|
||||
self.set_needs_update(needs_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +206,7 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
enabled: init.enabled,
|
||||
icon: None,
|
||||
input_sender: sender.input_sender().clone(),
|
||||
needs_update: init.needs_update,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue