Darktide Mod Manager #39
4 changed files with 78 additions and 9 deletions
|
@ -544,3 +544,13 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
|
||||||
|
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(state))]
|
||||||
|
pub(crate) async fn delete_mod(state: State, info: &ModInfo) -> Result<()> {
|
||||||
|
let mod_dir = state.get_mod_dir().join(info.get_id());
|
||||||
|
fs::remove_dir_all(&mod_dir)
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -17,10 +17,12 @@ use druid::AppLauncher;
|
||||||
use druid::ExtEventSink;
|
use druid::ExtEventSink;
|
||||||
use druid::SingleUse;
|
use druid::SingleUse;
|
||||||
use druid::Target;
|
use druid::Target;
|
||||||
|
use engine::delete_mod;
|
||||||
use engine::import_mod;
|
use engine::import_mod;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use state::ACTION_FINISH_ADD_MOD;
|
use state::ACTION_FINISH_ADD_MOD;
|
||||||
|
use state::ACTION_FINISH_DELETE_SELECTED_MOD;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
@ -83,6 +85,26 @@ fn work_thread(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
AsyncAction::DeleteMod((state, info)) => tokio::spawn(async move {
|
||||||
|
if let Err(err) = delete_mod(state, &info).await {
|
||||||
|
tracing::error!(
|
||||||
|
"Failed to delete mod files. \
|
||||||
|
You might want to clean up the data directory manually. \
|
||||||
|
Reason: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
event_sink
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.submit_command(
|
||||||
|
ACTION_FINISH_DELETE_SELECTED_MOD,
|
||||||
|
SingleUse::new(info),
|
||||||
|
Target::Auto,
|
||||||
|
)
|
||||||
|
.expect("failed to send command");
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,12 +3,14 @@ use druid::widget::{
|
||||||
Align, Button, CrossAxisAlignment, Flex, Label, List, MainAxisAlignment, Maybe, Scroll, Split,
|
Align, Button, CrossAxisAlignment, Flex, Label, List, MainAxisAlignment, Maybe, Scroll, Split,
|
||||||
TextBox, ViewSwitcher,
|
TextBox, ViewSwitcher,
|
||||||
};
|
};
|
||||||
use druid::{lens, FileDialogOptions, FileSpec, Insets, LensExt, Widget, WidgetExt, WindowDesc};
|
use druid::{
|
||||||
|
lens, FileDialogOptions, FileSpec, Insets, LensExt, SingleUse, Widget, WidgetExt, WindowDesc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::state::{ModInfo, PathBufFormatter, State, View, ACTION_ADD_MOD};
|
use crate::state::{ModInfo, PathBufFormatter, State, View};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
ACTION_DELETE_SELECTED_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP,
|
ACTION_ADD_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD,
|
||||||
ACTION_SELECT_MOD, ACTION_START_DEPLOY,
|
ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY,
|
||||||
};
|
};
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::widget::ExtraWidgetExt;
|
use crate::widget::ExtraWidgetExt;
|
||||||
|
@ -163,7 +165,13 @@ fn build_mod_details() -> impl Widget<State> {
|
||||||
});
|
});
|
||||||
|
|
||||||
let button_delete_mod = Button::new("Delete Mod")
|
let button_delete_mod = Button::new("Delete Mod")
|
||||||
.on_click(|ctx, _state, _env| ctx.submit_command(ACTION_DELETE_SELECTED_MOD))
|
.on_click(|ctx, data: &mut Option<ModInfo>, _env| {
|
||||||
|
if let Some(info) = data {
|
||||||
|
ctx.submit_command(
|
||||||
|
ACTION_START_DELETE_SELECTED_MOD.with(SingleUse::new(info.clone())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
|
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
|
||||||
.lens(State::selected_mod);
|
.lens(State::selected_mod);
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,10 @@ pub(crate) const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action
|
||||||
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
|
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
|
||||||
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
|
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
|
||||||
Selector::new("dtmm.action.selected-mod-down");
|
Selector::new("dtmm.action.selected-mod-down");
|
||||||
pub(crate) const ACTION_DELETE_SELECTED_MOD: Selector =
|
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
|
||||||
Selector::new("dtmm.action.delete-selected-mod");
|
Selector::new("dtmm.action.srart-delete-selected-mod");
|
||||||
|
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
|
||||||
|
Selector::new("dtmm.action.finish-delete-selected-mod");
|
||||||
|
|
||||||
pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy");
|
pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy");
|
||||||
pub(crate) const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy");
|
pub(crate) const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy");
|
||||||
|
@ -284,6 +286,7 @@ impl<T: Data> Lens<Vector<T>, Vector<(usize, T)>> for IndexedVectorLens {
|
||||||
pub(crate) enum AsyncAction {
|
pub(crate) enum AsyncAction {
|
||||||
DeployMods(State),
|
DeployMods(State),
|
||||||
AddMod((State, FileInfo)),
|
AddMod((State, FileInfo)),
|
||||||
|
DeleteMod((State, ModInfo)),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Delegate {
|
pub(crate) struct Delegate {
|
||||||
|
@ -360,8 +363,34 @@ impl AppDelegate<State> for Delegate {
|
||||||
state.selected_mod_index = Some(i + 1);
|
state.selected_mod_index = Some(i + 1);
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
cmd if cmd.is(ACTION_DELETE_SELECTED_MOD) => {
|
cmd if cmd.is(ACTION_START_DELETE_SELECTED_MOD) => {
|
||||||
let Some(index) = state.selected_mod_index else {
|
let info = cmd
|
||||||
|
.get(ACTION_FINISH_DELETE_SELECTED_MOD)
|
||||||
|
.and_then(|info| info.take())
|
||||||
|
.expect("command type matched but didn't contain the expected value");
|
||||||
|
if self
|
||||||
|
.sender
|
||||||
|
.send(AsyncAction::DeleteMod((state.clone(), info)))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
state.is_deployment_in_progress = true;
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed to queue action to deploy mods");
|
||||||
|
}
|
||||||
|
|
||||||
|
Handled::Yes
|
||||||
|
}
|
||||||
|
cmd if cmd.is(ACTION_FINISH_DELETE_SELECTED_MOD) => {
|
||||||
|
let info = cmd
|
||||||
|
.get(ACTION_FINISH_DELETE_SELECTED_MOD)
|
||||||
|
.and_then(|info| info.take())
|
||||||
|
.expect("command type matched but didn't contain the expected value");
|
||||||
|
let mods = state.get_mods();
|
||||||
|
let found = mods
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, i)| i.get_id() == info.get_id());
|
||||||
|
let Some((index, _)) = found else {
|
||||||
return Handled::No;
|
return Handled::No;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue