Require preferences to be specified if plugin provides no default value

This commit is contained in:
Exidex 2024-04-01 16:40:50 +02:00
parent 0f3e33ec1a
commit d1cfbeb036
16 changed files with 539 additions and 112 deletions

2
Cargo.lock generated
View file

@ -1012,6 +1012,8 @@ dependencies = [
"anyhow",
"gix",
"prost 0.12.3",
"serde",
"serde_json",
"tonic",
"tonic-build",
]

View file

@ -9,6 +9,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.5",
"@types/react": "^18.2.35",
"@project-gauntlet/api": "*",
"@project-gauntlet/typings": "*",
"@project-gauntlet/deno": "*",
"rollup": "^4.3.0",

View file

@ -103,6 +103,25 @@ async function handleKeyboardEvent(event: NotReactsKeyboardEvent) {
}
}
async function checkRequiredPreferences(entrypointId: string): Promise<boolean> {
const pluginPreferencesRequired = InternalApi.plugin_preferences_required();
const entrypointPreferencesRequired = InternalApi.entrypoint_preferences_required(entrypointId);
return pluginPreferencesRequired || entrypointPreferencesRequired;
}
async function checkRequiredPreferencesAndAsk(entrypointId: string): Promise<boolean> {
const pluginPreferencesRequired = await InternalApi.plugin_preferences_required();
const entrypointPreferencesRequired = await InternalApi.entrypoint_preferences_required(entrypointId);
const required = pluginPreferencesRequired || entrypointPreferencesRequired;
if (required) {
InternalApi.show_preferences_required_view(entrypointId, pluginPreferencesRequired, entrypointPreferencesRequired)
}
return required;
}
async function runLoop() {
while (true) {
InternalApi.op_log_trace("plugin_loop", "Waiting for next plugin event...")
@ -127,6 +146,10 @@ async function runLoop() {
}
case "OpenView": {
try {
if (await checkRequiredPreferencesAndAsk(pluginEvent.entrypointId)) {
break;
}
const View: FC = (await import(`gauntlet:entrypoint?${pluginEvent.entrypointId}`)).default;
const { render } = await import("gauntlet:renderer");
latestRootUiWidget = render(pluginEvent.frontend, pluginEvent.entrypointId, "View", <View/>);
@ -137,6 +160,10 @@ async function runLoop() {
}
case "RunCommand": {
try {
if (await checkRequiredPreferencesAndAsk(pluginEvent.entrypointId)) {
break;
}
const command: () => void = (await import(`gauntlet:entrypoint?${pluginEvent.entrypointId}`)).default;
command()
} catch (e) {
@ -146,6 +173,10 @@ async function runLoop() {
}
case "RunGeneratedCommand": {
try {
if (await checkRequiredPreferencesAndAsk(pluginEvent.entrypointId)) {
break;
}
runGeneratedCommand(pluginEvent.entrypointId)
} catch (e) {
console.error("Error occurred when running a generated command", pluginEvent.entrypointId, e)
@ -153,14 +184,18 @@ async function runLoop() {
break;
}
case "OpenInlineView": {
const endpoint_id = InternalApi.op_inline_view_endpoint_id();
const endpointId = InternalApi.op_inline_view_endpoint_id();
if (endpointId) {
if (await checkRequiredPreferences(endpointId)) {
break;
}
if (endpoint_id) {
try {
const Handler: FC<{ text: string }> = (await import(`gauntlet:entrypoint?${endpoint_id}`)).default;
const Handler: FC<{ text: string }> = (await import(`gauntlet:entrypoint?${endpointId}`)).default;
const { render } = await import("gauntlet:renderer");
latestRootUiWidget = render("default", endpoint_id, "InlineView", <Handler text={pluginEvent.text}/>);
latestRootUiWidget = render("default", endpointId, "InlineView", <Handler text={pluginEvent.text}/>);
if (latestRootUiWidget.widgetChildren.length === 0) {
InternalApi.op_log_debug("plugin_loop", `Inline view rendered no children, clearing inline view...`)

View file

@ -97,6 +97,9 @@ interface InternalApi {
get_command_generator_entrypoint_ids(): Promise<string[]>
get_plugin_preferences(): Record<string, any>;
get_entrypoint_preferences(entrypointId: string): Record<string, any>;
plugin_preferences_required(): Promise<boolean>;
entrypoint_preferences_required(entrypointId: string): Promise<boolean>;
show_preferences_required_view(entrypointId: string, pluginPreferencesRequired: boolean, entrypointPreferencesRequired: boolean): void;
load_search_index(searchItems: AdditionalSearchItem[]): Promise<void>;

View file

@ -40,6 +40,12 @@ pub enum NativeUiRequestData {
top_level_view: bool,
container: NativeUiWidget,
},
ShowPreferenceRequiredView {
plugin_id: PluginId,
entrypoint_id: EntrypointId,
plugin_preferences_required: bool,
entrypoint_preferences_required: bool
},
}
#[derive(Debug, Clone)]

View file

@ -1,7 +1,7 @@
use tonic::{Request, Response, Status};
use common::model::{EntrypointId, PluginId, RenderLocation};
use common::rpc::{RpcClearInlineViewRequest, RpcClearInlineViewResponse, RpcRenderLocation, RpcReplaceViewRequest, RpcReplaceViewResponse, RpcShowWindowRequest, RpcShowWindowResponse};
use common::rpc::{RpcClearInlineViewRequest, RpcClearInlineViewResponse, RpcRenderLocation, RpcReplaceViewRequest, RpcReplaceViewResponse, RpcShowPreferenceRequiredViewRequest, RpcShowPreferenceRequiredViewResponse, RpcShowWindowRequest, RpcShowWindowResponse};
use common::rpc::rpc_frontend_server::RpcFrontend;
use utils::channel::RequestSender;
@ -71,6 +71,27 @@ impl RpcFrontend for RpcFrontendServerImpl {
Ok(Response::new(RpcShowWindowResponse::default()))
}
async fn show_preference_required_view(&self, request: Request<RpcShowPreferenceRequiredViewRequest>) -> Result<Response<RpcShowPreferenceRequiredViewResponse>, Status> {
let request = request.into_inner();
let plugin_id = request.plugin_id;
let entrypoint_id = request.entrypoint_id;
let plugin_preferences_required = request.plugin_preferences_required;
let entrypoint_preferences_required = request.entrypoint_preferences_required;
let data = NativeUiRequestData::ShowPreferenceRequiredView {
plugin_id: PluginId::from_string(plugin_id),
entrypoint_id: EntrypointId::from_string(entrypoint_id),
plugin_preferences_required,
entrypoint_preferences_required,
};
match self.context_tx.send_receive(data).await {
NativeUiResponseData::Nothing => {}
};
Ok(Response::new(RpcShowPreferenceRequiredViewResponse::default()))
}
}

View file

@ -7,7 +7,7 @@ use iced::futures::SinkExt;
use iced::keyboard::Key;
use iced::keyboard::key::Named;
use iced::Application;
use iced::widget::{column, container, horizontal_rule, scrollable, text_input};
use iced::widget::{button, column, container, horizontal_rule, scrollable, text, text_input};
use iced::widget::text_input::focus;
use iced::window::{change_level, Level, Position, reposition};
use iced_aw::graphics::icons;
@ -18,7 +18,7 @@ use tonic::transport::Server;
use client_context::ClientContext;
use common::model::{EntrypointId, PluginId, PropertyValue, RenderLocation};
use common::rpc::{BackendClient, RpcEntrypointTypeSearchResult, RpcEventKeyboardEvent, RpcEventRenderView, RpcEventRunCommand, RpcEventRunGeneratedCommand, RpcEventViewEvent, RpcRequestRunCommandRequest, RpcRequestRunGeneratedCommandRequest, RpcRequestViewRenderRequest, RpcSearchRequest, RpcSendKeyboardEventRequest, RpcSendViewEventRequest, RpcUiPropertyValue, RpcUiWidgetId};
use common::rpc::{BackendClient, RpcEntrypointTypeSearchResult, RpcEventKeyboardEvent, RpcEventRenderView, RpcEventRunCommand, RpcEventRunGeneratedCommand, RpcEventViewEvent, RpcOpenSettingsWindowPreferencesRequest, RpcRequestRunCommandRequest, RpcRequestRunGeneratedCommandRequest, RpcRequestViewRenderRequest, RpcSearchRequest, RpcSendKeyboardEventRequest, RpcSendViewEventRequest, RpcUiPropertyValue, RpcUiWidgetId};
use common::rpc::rpc_backend_client::RpcBackendClient;
use common::rpc::rpc_frontend_server::RpcFrontendServer;
use common::rpc::rpc_ui_property_value::Value;
@ -46,18 +46,26 @@ pub struct AppModel {
search_results: Vec<NativeUiSearchResult>,
request_rx: Arc<TokioRwLock<RequestReceiver<NativeUiRequestData, NativeUiResponseData>>>,
search_field_id: text_input::Id,
view_data: Option<ViewData>,
plugin_view_data: Option<PluginViewData>,
prompt: Option<String>,
waiting_for_next_unfocus: bool,
global_hotkey_manager: GlobalHotKeyManager,
preference_required_view: Option<PreferenceRequiredViewData>,
}
struct ViewData {
struct PluginViewData {
top_level_view: bool,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
}
struct PreferenceRequiredViewData {
plugin_id: PluginId,
entrypoint_id: EntrypointId,
plugin_preferences_required: bool,
entrypoint_preferences_required: bool,
}
#[derive(Debug, Clone)]
pub enum AppMsg {
OpenView {
@ -85,6 +93,16 @@ pub enum AppMsg {
FontLoaded(Result<(), font::Error>),
ShowWindow,
HideWindow,
ShowPreferenceRequiredView {
plugin_id: PluginId,
entrypoint_id: EntrypointId,
plugin_preferences_required: bool,
entrypoint_preferences_required: bool
},
OpenSettingsPreferences {
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
},
}
const WINDOW_WIDTH: f32 = 650.0;
@ -147,9 +165,10 @@ impl Application for AppModel {
search_results: vec![],
search_field_id: text_input::Id::unique(),
prompt: None,
view_data: None,
plugin_view_data: None,
waiting_for_next_unfocus: false,
global_hotkey_manager,
preference_required_view: None,
},
Command::batch([
change_level(window::Id::MAIN, Level::AlwaysOnTop),
@ -166,7 +185,7 @@ impl Application for AppModel {
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
AppMsg::OpenView { plugin_id, entrypoint_id } => {
self.view_data.replace(ViewData {
self.plugin_view_data.replace(PluginViewData {
top_level_view: true,
plugin_id: plugin_id.clone(),
entrypoint_id: entrypoint_id.clone(),
@ -235,7 +254,7 @@ impl Application for AppModel {
Command::none()
}
AppMsg::SetTopLevelView(top_level_view) => {
match &mut self.view_data {
match &mut self.plugin_view_data {
None => Command::none(),
Some(view_data) => {
view_data.top_level_view = top_level_view;
@ -253,7 +272,7 @@ impl Application for AppModel {
Key::Named(Named::ArrowDown) => iced::widget::focus_next(),
Key::Named(Named::Escape) => self.previous_view(),
Key::Character(char) => {
if let Some(_) = self.view_data {
if let Some(_) = self.plugin_view_data {
let (plugin_id, entrypoint_id) = {
let client_context = self.client_context.read().expect("lock is poisoned");
(client_context.get_view_plugin_id(), client_context.get_view_entrypoint_id())
@ -362,11 +381,89 @@ impl Application for AppModel {
}
AppMsg::ShowWindow => self.show_window(),
AppMsg::HideWindow => self.hide_window(),
AppMsg::ShowPreferenceRequiredView {
plugin_id,
entrypoint_id,
plugin_preferences_required,
entrypoint_preferences_required
} => {
self.preference_required_view = Some(PreferenceRequiredViewData {
plugin_id,
entrypoint_id,
plugin_preferences_required,
entrypoint_preferences_required,
});
Command::none()
}
AppMsg::OpenSettingsPreferences { plugin_id, entrypoint_id, } => {
let mut backend_client = self.backend_client.clone();
Command::perform(async move {
let request = RpcOpenSettingsWindowPreferencesRequest {
plugin_id: plugin_id.to_string(),
entrypoint_id: entrypoint_id.map(|val| val.to_string()).unwrap_or_default(),
};
backend_client.open_settings_window_preferences(Request::new(request))
.await
.unwrap();
}, |_| AppMsg::Noop)
}
}
}
fn view(&self) -> Element<'_, Self::Message> {
match &self.view_data {
if let Some(view_data) = &self.preference_required_view {
let PreferenceRequiredViewData { plugin_id, entrypoint_id, plugin_preferences_required, entrypoint_preferences_required } = view_data;
let (description_text, msg) = match (plugin_preferences_required, entrypoint_preferences_required) {
(true, true) => {
// TODO do not show "entrypoint" name to user
let description_text = "Before using, plugin and entrypoint preferences need to be specified";
// note:
// we open plugin view and not entrypoint even though both need to be specified
let msg = AppMsg::OpenSettingsPreferences { plugin_id: plugin_id.clone(), entrypoint_id: None };
(description_text, msg)
}
(false, true) => {
// TODO do not show "entrypoint" name to user
let description_text = "Before using, entrypoint preferences need to be specified";
let msg = AppMsg::OpenSettingsPreferences { plugin_id: plugin_id.clone(), entrypoint_id: Some(entrypoint_id.clone()) };
(description_text, msg)
}
(true, false) => {
let description_text = "Before using, plugin preferences need to be specified";
let msg = AppMsg::OpenSettingsPreferences { plugin_id: plugin_id.clone(), entrypoint_id: None };
(description_text, msg)
}
(false, false) => unreachable!()
};
let description: Element<_> = text(description_text)
.into();
let button_label: Element<_> = text("Open Settings")
.into();
let button: Element<_> = button(button_label)
.on_press(msg)
.into();
let content: Element<_> = column([
description,
button
]).into();
let content: Element<_> = container(content)
.style(ContainerStyle::Background)
.height(Length::Fixed(WINDOW_HEIGHT))
.width(Length::Fixed(WINDOW_WIDTH))
.into();
return content
}
match &self.plugin_view_data {
None => {
let input: Element<_> = text_input("Search...", self.prompt.as_ref().unwrap_or(&"".to_owned()))
.on_input(AppMsg::PromptChanged)
@ -440,7 +537,7 @@ impl Application for AppModel {
// element.explain(iced::color!(0xFF0000))
element
}
Some(ViewData { plugin_id, entrypoint_id: _, top_level_view: _ }) => {
Some(PluginViewData { plugin_id, entrypoint_id: _, top_level_view: _ }) => {
let container_element: Element<_> = view_container(self.client_context.clone(), plugin_id.to_owned())
.into();
@ -504,13 +601,16 @@ impl Application for AppModel {
impl AppModel {
fn hide_window(&mut self) -> Command<AppMsg> {
self.prompt = None;
self.view_data = None;
self.plugin_view_data = None;
self.search_results = vec![];
self.close_preference_required_view();
window::change_mode(window::Id::MAIN, window::Mode::Hidden)
}
fn show_window(&mut self) -> Command<AppMsg> {
self.close_preference_required_view();
Command::batch([
window::change_mode(window::Id::MAIN, window::Mode::Windowed),
window::gain_focus(window::Id::MAIN),
@ -520,20 +620,24 @@ impl AppModel {
])
}
fn close_preference_required_view(&mut self) {
self.preference_required_view = None;
}
fn previous_view(&mut self) -> Command<AppMsg> {
match &self.view_data {
match &self.plugin_view_data {
None => {
self.hide_window()
}
Some(ViewData { top_level_view: true, .. }) => {
self.view_data.take();
Some(PluginViewData { top_level_view: true, .. }) => {
self.plugin_view_data.take();
Command::batch([
reposition(window::Id::MAIN, Position::Centered, Size::new(WINDOW_WIDTH, WINDOW_HEIGHT)),
window::resize(window::Id::MAIN, Size::new(WINDOW_WIDTH, WINDOW_HEIGHT))
])
}
Some(ViewData { top_level_view: false, plugin_id, entrypoint_id }) => {
Some(PluginViewData { top_level_view: false, plugin_id, entrypoint_id }) => {
self.open_view(plugin_id.clone(), entrypoint_id.clone())
}
}
@ -656,6 +760,21 @@ async fn request_loop(
NativeUiRequestData::ShowWindow => {
app_msg = AppMsg::ShowWindow;
responder.respond(NativeUiResponseData::Nothing)
}
NativeUiRequestData::ShowPreferenceRequiredView {
plugin_id,
entrypoint_id,
plugin_preferences_required,
entrypoint_preferences_required
} => {
app_msg = AppMsg::ShowPreferenceRequiredView {
plugin_id,
entrypoint_id,
plugin_preferences_required,
entrypoint_preferences_required
};
responder.respond(NativeUiResponseData::Nothing)
}
}

View file

@ -7,6 +7,8 @@ gix = { version = "0.52.0", features = ["blocking-http-transport-curl"] }
anyhow = { version = "1", features = ["backtrace"] }
tonic = "0.11.0"
prost = "0.12.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[build-dependencies]
tonic-build = "0.11.0"

View file

@ -1,4 +1,5 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use tonic::transport::Channel;
@ -218,3 +219,23 @@ pub fn plugin_preference_user_data_from_npb(value: RpcNoProtoBufPluginPreference
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum SettingsEnvData {
OpenPluginPreferences {
plugin_id: String,
},
OpenEntrypointPreferences {
plugin_id: String,
entrypoint_id: String,
}
}
pub fn settings_env_data_to_string(data: SettingsEnvData) -> String {
serde_json::to_string(&data).expect("unable to serialize settings env data")
}
pub fn settings_env_data_from_string(data: String) -> SettingsEnvData {
serde_json::from_str(&data).expect("unable to serialize settings env data")
}

View file

@ -14,10 +14,12 @@ use iced_table::table;
use tonic::Request;
use common::model::{EntrypointId, PluginId};
use common::rpc::{BackendClient, plugin_preference_user_data_from_npb, plugin_preference_user_data_to_npb, RpcDownloadPluginRequest, RpcDownloadStatus, RpcDownloadStatusRequest, RpcEntrypointTypeSettings, RpcNoProtoBufPluginPreferenceUserData, RpcPluginPreference, RpcPluginPreferenceValueType, RpcPluginsRequest, RpcSetEntrypointStateRequest, RpcSetPluginStateRequest, RpcSetPreferenceValueRequest};
use common::rpc::{BackendClient, plugin_preference_user_data_from_npb, plugin_preference_user_data_to_npb, RpcDownloadPluginRequest, RpcDownloadStatus, RpcDownloadStatusRequest, RpcEntrypointTypeSettings, RpcNoProtoBufPluginPreferenceUserData, RpcPluginPreference, RpcPluginPreferenceValueType, RpcPluginsRequest, RpcSetEntrypointStateRequest, RpcSetPluginStateRequest, RpcSetPreferenceValueRequest, settings_env_data_from_string, SettingsEnvData};
use common::rpc::rpc_backend_client::RpcBackendClient;
use common::rpc::rpc_ui_property_value::Value;
const SETTINGS_ENV: &'static str = "GAUNTLET_INTERNAL_SETTINGS";
pub fn run() {
ManagementAppModel::run(Settings {
id: None,
@ -46,6 +48,10 @@ enum ManagementAppMsg {
TableSyncHeader(scrollable::AbsoluteOffset),
FontLoaded(Result<(), font::Error>),
PluginsReloaded(HashMap<PluginId, Plugin>),
InitialPluginsReloaded {
plugins: HashMap<PluginId, Plugin>,
select_item: SelectedItem,
},
ToggleShowEntrypoints {
plugin_id: PluginId,
},
@ -207,6 +213,21 @@ impl Application for ManagementAppModel {
anyhow::Ok(RpcBackendClient::connect("http://127.0.0.1:42320").await?)
}).unwrap();
let settings_env_data = std::env::var(SETTINGS_ENV)
.map(|val| settings_env_data_from_string(val))
.ok();
let select_item = match settings_env_data {
None => SelectedItem::None,
Some(SettingsEnvData::OpenEntrypointPreferences { plugin_id, entrypoint_id }) => SelectedItem::Entrypoint {
plugin_id: PluginId::from_string(plugin_id),
entrypoint_id: EntrypointId::from_string(entrypoint_id),
},
Some(SettingsEnvData::OpenPluginPreferences { plugin_id }) => SelectedItem::Plugin {
plugin_id: PluginId::from_string(plugin_id),
},
};
(
ManagementAppModel {
backend_client: backend_client.clone(),
@ -230,8 +251,11 @@ impl Application for ManagementAppModel {
async {
reload_plugins(backend_client).await
},
ManagementAppMsg::PluginsReloaded,
)
|plugins| ManagementAppMsg::InitialPluginsReloaded {
plugins,
select_item
},
),
]),
)
}
@ -256,75 +280,16 @@ impl Application for ManagementAppModel {
Command::none()
}
ManagementAppMsg::PluginsReloaded(plugins) => {
self.preference_user_data = plugins.iter()
.map(|(plugin_id, plugin)| {
let mut result = vec![];
for (name, user_data) in &plugin.preferences_user_data {
result.push(((plugin_id.clone(), None, name.clone()), user_data.clone()))
}
for (entrypoint_id, entrypoint) in &plugin.entrypoints {
for (name, user_data) in &entrypoint.preferences_user_data {
result.push(((plugin_id.clone(), Some(entrypoint_id.clone()), name.clone()), user_data.clone()))
}
}
result
})
.flatten()
.collect();
let plugins = Rc::new(RefCell::new(plugins));
self.plugins = plugins.clone();
let plugin_refs = plugins.borrow();
let mut plugin_refs: Vec<_> = plugin_refs
.iter()
.map(|(_, plugin)| plugin)
.collect();
plugin_refs.sort_by_key(|plugin| &plugin.plugin_name);
self.rows = plugin_refs
.iter()
.flat_map(|plugin| {
let mut result = vec![];
result.push(Row::Plugin {
plugins: plugins.clone(),
plugin_id: plugin.plugin_id.clone()
});
if plugin.show_entrypoints {
let mut entrypoints: Vec<_> = plugin.entrypoints
.iter()
.map(|(_, entrypoint)| entrypoint)
.collect();
entrypoints.sort_by_key(|entrypoint| &entrypoint.entrypoint_name);
let mut entrypoints: Vec<_> = entrypoints
.iter()
.map(|entrypoint| {
Row::Entrypoint {
plugins: plugins.clone(),
plugin_id: plugin.plugin_id.clone(),
entrypoint_id: entrypoint.entrypoint_id.clone(),
}
})
.collect();
result.append(&mut entrypoints);
}
result
})
.collect();
self.apply_plugin_reload(plugins);
Command::none()
}
ManagementAppMsg::InitialPluginsReloaded { plugins, select_item } => {
self.apply_plugin_reload(plugins);
self.selected_item = select_item;
Command::none()
},
ManagementAppMsg::SelectItem(selected_item) => {
self.selected_item = selected_item;
Command::none()
@ -1481,6 +1446,76 @@ fn preferences_ui<'a>(
column_content
}
impl ManagementAppModel {
fn apply_plugin_reload(&mut self, plugins: HashMap<PluginId, Plugin>) {
self.preference_user_data = plugins.iter()
.map(|(plugin_id, plugin)| {
let mut result = vec![];
for (name, user_data) in &plugin.preferences_user_data {
result.push(((plugin_id.clone(), None, name.clone()), user_data.clone()))
}
for (entrypoint_id, entrypoint) in &plugin.entrypoints {
for (name, user_data) in &entrypoint.preferences_user_data {
result.push(((plugin_id.clone(), Some(entrypoint_id.clone()), name.clone()), user_data.clone()))
}
}
result
})
.flatten()
.collect();
let plugins = Rc::new(RefCell::new(plugins));
self.plugins = plugins.clone();
let plugin_refs = plugins.borrow();
let mut plugin_refs: Vec<_> = plugin_refs
.iter()
.map(|(_, plugin)| plugin)
.collect();
plugin_refs.sort_by_key(|plugin| &plugin.plugin_name);
self.rows = plugin_refs
.iter()
.flat_map(|plugin| {
let mut result = vec![];
result.push(Row::Plugin {
plugins: plugins.clone(),
plugin_id: plugin.plugin_id.clone()
});
if plugin.show_entrypoints {
let mut entrypoints: Vec<_> = plugin.entrypoints
.iter()
.map(|(_, entrypoint)| entrypoint)
.collect();
entrypoints.sort_by_key(|entrypoint| &entrypoint.entrypoint_name);
let mut entrypoints: Vec<_> = entrypoints
.iter()
.map(|entrypoint| {
Row::Entrypoint {
plugins: plugins.clone(),
plugin_id: plugin.plugin_id.clone(),
entrypoint_id: entrypoint.entrypoint_id.clone(),
}
})
.collect();
result.append(&mut entrypoints);
}
result
})
.collect();
}
}
async fn reload_plugins(mut backend_client: BackendClient) -> HashMap<PluginId, Plugin> {
backend_client.plugins(Request::new(RpcPluginsRequest::default()))

View file

@ -12,6 +12,7 @@ pub(in crate) mod model;
mod dirs;
const FRONTEND_ENV: &'static str = "GAUNTLET_INTERNAL_FRONTEND";
const SETTINGS_ENV: &'static str = "GAUNTLET_INTERNAL_SETTINGS";
pub fn start_server() {
if std::env::var(FRONTEND_ENV).is_ok() {

View file

@ -20,7 +20,12 @@ pub enum JsUiRequestData {
top_level_view: bool,
container: IntermediateUiWidget,
},
ClearInlineView
ClearInlineView,
ShowPreferenceRequiredView {
entrypoint_id: EntrypointId,
plugin_preferences_required: bool,
entrypoint_preferences_required: bool
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
@ -200,9 +205,9 @@ fn from_intermediate_to_rpc_properties(value: HashMap<String, PropertyValue>) ->
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PreferenceUserData {
Number(Option<f64>),
String(Option<String>),
Bool(Option<bool>),
Number(f64),
String(String),
Bool(bool),
ListOfStrings(Vec<String>),
ListOfNumbers(Vec<f64>),
}

View file

@ -22,7 +22,7 @@ use tonic::Request;
use tonic::transport::Channel;
use common::model::{EntrypointId, PluginId, PropertyValue, RenderLocation};
use common::rpc::{FrontendClient, RpcClearInlineViewRequest, RpcRenderLocation, RpcReplaceViewRequest, RpcUiPropertyValue, RpcUiWidgetId};
use common::rpc::{FrontendClient, RpcClearInlineViewRequest, RpcRenderLocation, RpcReplaceViewRequest, RpcShowPreferenceRequiredViewRequest, RpcUiPropertyValue, RpcUiWidgetId};
use common::rpc::rpc_frontend_client::RpcFrontendClient;
use common::rpc::rpc_frontend_server::RpcFrontend;
use component_model::{Children, Component, create_component_model, PropertyType};
@ -427,6 +427,9 @@ deno_core::extension!(
load_search_index,
get_command_generator_entrypoint_ids,
fetch_action_id_for_shortcut,
plugin_preferences_required,
entrypoint_preferences_required,
show_preferences_required_view,
],
options = {
event_receiver: EventReceiver,
@ -536,6 +539,70 @@ fn asset_data_blocking(state: Rc<RefCell<OpState>>, path: String) -> anyhow::Res
})
}
#[op]
async fn plugin_preferences_required(state: Rc<RefCell<OpState>>) -> anyhow::Result<bool> {
let (plugin_id, repository) = {
let state = state.borrow();
let plugin_id = state
.borrow::<PluginData>()
.plugin_id()
.clone();
let repository = state
.borrow::<DataDbRepository>()
.clone();
(plugin_id, repository)
};
let DbReadPlugin { preferences, preferences_user_data, .. } = repository
.get_plugin_by_id(&plugin_id.to_string()).await?;
Ok(all_preferences_required(preferences, preferences_user_data))
}
#[op]
async fn entrypoint_preferences_required(state: Rc<RefCell<OpState>>, entrypoint_id: String) -> anyhow::Result<bool> {
let (plugin_id, repository) = {
let state = state.borrow();
let plugin_id = state
.borrow::<PluginData>()
.plugin_id()
.clone();
let repository = state
.borrow::<DataDbRepository>()
.clone();
(plugin_id, repository)
};
let DbReadPluginEntrypoint { preferences, preferences_user_data, .. } = repository
.get_entrypoint_by_id(&plugin_id.to_string(), &entrypoint_id).await?;
Ok(all_preferences_required(preferences, preferences_user_data))
}
#[op]
fn show_preferences_required_view(state: Rc<RefCell<OpState>>, entrypoint_id: String, plugin_preferences_required: bool, entrypoint_preferences_required: bool) -> anyhow::Result<()> {
let data = JsUiRequestData::ShowPreferenceRequiredView {
entrypoint_id: EntrypointId::from_string(entrypoint_id),
plugin_preferences_required,
entrypoint_preferences_required
};
match make_request(&state, data).context("ShowPreferenceRequiredView frontend response")? {
JsUiResponseData::Nothing => {
tracing::trace!(target = "renderer_rs", "Calling show_preferences_required_view returned");
Ok(())
}
value @ _ => panic!("unsupported response type {:?}", value),
}
}
#[op]
async fn op_plugin_get_pending_event(state: Rc<RefCell<OpState>>) -> anyhow::Result<JsUiEvent> {
@ -777,25 +844,23 @@ fn preferences_to_js(
preferences.into_iter()
.map(|(name, preference)| {
let user_data = match preferences_user_data.remove(&name) {
None => {
match preference {
DbPluginPreference::Number { default, .. } => PreferenceUserData::Number(default),
DbPluginPreference::String { default, .. } => PreferenceUserData::String(default),
DbPluginPreference::Enum { default, .. } => PreferenceUserData::String(default),
DbPluginPreference::Bool { default, .. } => PreferenceUserData::Bool(default),
DbPluginPreference::ListOfStrings { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])),
DbPluginPreference::ListOfNumbers { default, .. } => PreferenceUserData::ListOfNumbers(default.unwrap_or(vec![])),
DbPluginPreference::ListOfEnums { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])),
}
None => match preference {
DbPluginPreference::Number { default, .. } => PreferenceUserData::Number(default.expect("at this point preference should always have value")),
DbPluginPreference::String { default, .. } => PreferenceUserData::String(default.expect("at this point preference should always have value")),
DbPluginPreference::Enum { default, .. } => PreferenceUserData::String(default.expect("at this point preference should always have value")),
DbPluginPreference::Bool { default, .. } => PreferenceUserData::Bool(default.expect("at this point preference should always have value")),
DbPluginPreference::ListOfStrings { default, .. } => PreferenceUserData::ListOfStrings(default.expect("at this point preference should always have value")),
DbPluginPreference::ListOfNumbers { default, .. } => PreferenceUserData::ListOfNumbers(default.expect("at this point preference should always have value")),
DbPluginPreference::ListOfEnums { default, .. } => PreferenceUserData::ListOfStrings(default.expect("at this point preference should always have value")),
}
Some(user_data) => match user_data {
DbPluginPreferenceUserData::Number { value } => PreferenceUserData::Number(value),
DbPluginPreferenceUserData::String { value } => PreferenceUserData::String(value),
DbPluginPreferenceUserData::Enum { value } => PreferenceUserData::String(value),
DbPluginPreferenceUserData::Bool { value } => PreferenceUserData::Bool(value),
DbPluginPreferenceUserData::ListOfStrings { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])),
DbPluginPreferenceUserData::ListOfNumbers { value } => PreferenceUserData::ListOfNumbers(value.unwrap_or(vec![])),
DbPluginPreferenceUserData::ListOfEnums { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])),
DbPluginPreferenceUserData::Number { value } => PreferenceUserData::Number(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::String { value } => PreferenceUserData::String(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::Enum { value } => PreferenceUserData::String(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::Bool { value } => PreferenceUserData::Bool(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::ListOfStrings { value } => PreferenceUserData::ListOfStrings(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::ListOfNumbers { value } => PreferenceUserData::ListOfNumbers(value.expect("at this point preference should always have value")),
DbPluginPreferenceUserData::ListOfEnums { value } => PreferenceUserData::ListOfStrings(value.expect("at this point preference should always have value")),
}
};
@ -1020,6 +1085,21 @@ async fn make_request_async(plugin_id: PluginId, frontend_client: &mut FrontendC
nothing
}
JsUiRequestData::ShowPreferenceRequiredView { plugin_preferences_required, entrypoint_preferences_required, entrypoint_id } => {
let request = Request::new(RpcShowPreferenceRequiredViewRequest {
plugin_id: plugin_id.to_string(),
entrypoint_id: entrypoint_id.to_string(),
plugin_preferences_required,
entrypoint_preferences_required,
});
let nothing = frontend_client.show_preference_required_view(request)
.await
.map(|_| JsUiResponseData::Nothing)
.map_err(|err| err.into());
nothing
}
}
}
@ -1111,6 +1191,45 @@ fn from_js_to_intermediate_properties(
Ok(vec.into_iter().collect())
}
fn all_preferences_required(preferences: HashMap<String, DbPluginPreference>, preferences_user_data: HashMap<String, DbPluginPreferenceUserData>) -> bool {
for (name, preference) in preferences {
match preferences_user_data.get(&name) {
None => {
let no_default = match preference {
DbPluginPreference::Number { default, .. } => default.is_none(),
DbPluginPreference::String { default, .. } => default.is_none(),
DbPluginPreference::Enum { default, .. } => default.is_none(),
DbPluginPreference::Bool { default, .. } => default.is_none(),
DbPluginPreference::ListOfStrings { default, .. } => default.is_none(),
DbPluginPreference::ListOfNumbers { default, .. } => default.is_none(),
DbPluginPreference::ListOfEnums { default, .. } => default.is_none(),
};
if no_default {
return true
}
}
Some(preference) => {
let no_value = match preference {
DbPluginPreferenceUserData::Number { value } => value.is_none(),
DbPluginPreferenceUserData::String { value } => value.is_none(),
DbPluginPreferenceUserData::Enum { value } => value.is_none(),
DbPluginPreferenceUserData::Bool { value } => value.is_none(),
DbPluginPreferenceUserData::ListOfStrings { value } => value.is_none(),
DbPluginPreferenceUserData::ListOfNumbers { value } => value.is_none(),
DbPluginPreferenceUserData::ListOfEnums { value } => value.is_none(),
};
if no_value {
return true
}
}
}
}
false
}
pub struct PluginData {
plugin_id: PluginId,
inline_view_entrypoint_id: Option<String>

View file

@ -3,9 +3,10 @@ use std::collections::HashMap;
use tonic::{Request, Response, Status};
use common::model::{DownloadStatus, EntrypointId, PluginId};
use common::rpc::{RpcDownloadPluginRequest, RpcDownloadPluginResponse, RpcDownloadStatus, RpcDownloadStatusRequest, RpcDownloadStatusResponse, RpcDownloadStatusValue, RpcEntrypointTypeSearchResult, RpcEventRenderView, RpcEventRunCommand, RpcEventViewEvent, RpcPlugin, RpcPluginsRequest, RpcPluginsResponse, RpcRequestRunCommandRequest, RpcRequestRunCommandResponse, RpcRequestRunGeneratedCommandRequest, RpcRequestRunGeneratedCommandResponse, RpcRequestViewRenderRequest, RpcRequestViewRenderResponse, RpcSaveLocalPluginRequest, RpcSaveLocalPluginResponse, RpcSearchRequest, RpcSearchResponse, RpcSearchResult, RpcSendKeyboardEventRequest, RpcSendKeyboardEventResponse, RpcSendViewEventRequest, RpcSendViewEventResponse, RpcSetEntrypointStateRequest, RpcSetEntrypointStateResponse, RpcSetPluginStateRequest, RpcSetPluginStateResponse, RpcSetPreferenceValueRequest, RpcSetPreferenceValueResponse};
use common::rpc::{RpcDownloadPluginRequest, RpcDownloadPluginResponse, RpcDownloadStatus, RpcDownloadStatusRequest, RpcDownloadStatusResponse, RpcDownloadStatusValue, RpcEntrypointTypeSearchResult, RpcEventRenderView, RpcEventRunCommand, RpcEventViewEvent, RpcOpenSettingsWindowPreferencesRequest, RpcOpenSettingsWindowPreferencesResponse, RpcOpenSettingsWindowRequest, RpcOpenSettingsWindowResponse, RpcPlugin, RpcPluginsRequest, RpcPluginsResponse, RpcRequestRunCommandRequest, RpcRequestRunCommandResponse, RpcRequestRunGeneratedCommandRequest, RpcRequestRunGeneratedCommandResponse, RpcRequestViewRenderRequest, RpcRequestViewRenderResponse, RpcSaveLocalPluginRequest, RpcSaveLocalPluginResponse, RpcSearchRequest, RpcSearchResponse, RpcSearchResult, RpcSendKeyboardEventRequest, RpcSendKeyboardEventResponse, RpcSendViewEventRequest, RpcSendViewEventResponse, RpcSetEntrypointStateRequest, RpcSetEntrypointStateResponse, RpcSetPluginStateRequest, RpcSetPluginStateResponse, RpcSetPreferenceValueRequest, RpcSetPreferenceValueResponse, settings_env_data_to_string, SettingsEnvData};
use common::rpc::rpc_backend_server::{RpcBackend, RpcBackendServer};
use crate::{FRONTEND_ENV, SETTINGS_ENV};
use crate::model::from_rpc_to_intermediate_value;
use crate::plugins::ApplicationManager;
use crate::search::{SearchIndex, SearchIndexPluginEntrypointType};
@ -230,6 +231,35 @@ impl RpcBackend for RpcBackendServerImpl {
Ok(Response::new(response))
}
async fn open_settings_window(&self, _: Request<RpcOpenSettingsWindowRequest>) -> Result<Response<RpcOpenSettingsWindowResponse>, Status> {
std::process::Command::new(std::env::current_exe()?)
.args(["management"])
.spawn()
.expect("failed to execute settings process");
Ok(Response::new(RpcOpenSettingsWindowResponse::default()))
}
async fn open_settings_window_preferences(&self, request: Request<RpcOpenSettingsWindowPreferencesRequest>) -> Result<Response<RpcOpenSettingsWindowPreferencesResponse>, Status> {
let request = request.into_inner();
let plugin_id = request.plugin_id;
let entrypoint_id = request.entrypoint_id;
let data = if entrypoint_id.is_empty() {
SettingsEnvData::OpenPluginPreferences { plugin_id }
} else {
SettingsEnvData::OpenEntrypointPreferences { plugin_id, entrypoint_id }
};
std::process::Command::new(std::env::current_exe()?)
.args(["management"])
.env(SETTINGS_ENV, settings_env_data_to_string(data))
.spawn()
.expect("failed to execute settings process");
Ok(Response::new(RpcOpenSettingsWindowPreferencesResponse::default()))
}
async fn save_local_plugin(&self, request: Request<RpcSaveLocalPluginRequest>) -> Result<Response<RpcSaveLocalPluginResponse>, Status> {
let request = request.into_inner();
let path = request.path;

View file

@ -29,6 +29,9 @@ service RpcBackend {
rpc DownloadStatus (RpcDownloadStatusRequest) returns (RpcDownloadStatusResponse);
rpc OpenSettingsWindow (RpcOpenSettingsWindowRequest) returns (RpcOpenSettingsWindowResponse);
rpc OpenSettingsWindowPreferences (RpcOpenSettingsWindowPreferencesRequest) returns (RpcOpenSettingsWindowPreferencesResponse);
// dev tools
rpc SaveLocalPlugin (RpcSaveLocalPluginRequest) returns (RpcSaveLocalPluginResponse);
}
@ -123,6 +126,19 @@ message RpcDownloadStatusResponse {
map<string, RpcDownloadStatusValue> status_per_plugin = 1;
}
message RpcOpenSettingsWindowRequest {
}
message RpcOpenSettingsWindowResponse {
}
message RpcOpenSettingsWindowPreferencesRequest {
string plugin_id = 1;
string entrypoint_id = 2;
}
message RpcOpenSettingsWindowPreferencesResponse {
}
message RpcSearchResult {
string plugin_id = 1;
string plugin_name = 2;

View file

@ -6,6 +6,7 @@ service RpcFrontend {
rpc ReplaceView (RpcReplaceViewRequest) returns (RpcReplaceViewResponse);
rpc ClearInlineView (RpcClearInlineViewRequest) returns (RpcClearInlineViewResponse);
rpc ShowWindow (RpcShowWindowRequest) returns (RpcShowWindowResponse);
rpc ShowPreferenceRequiredView (RpcShowPreferenceRequiredViewRequest) returns (RpcShowPreferenceRequiredViewResponse);
}
message RpcShowWindowRequest {
@ -31,6 +32,16 @@ message RpcClearInlineViewRequest {
message RpcClearInlineViewResponse {
}
message RpcShowPreferenceRequiredViewRequest {
string plugin_id = 1;
string entrypoint_id = 2;
bool plugin_preferences_required = 3;
bool entrypoint_preferences_required = 4;
}
message RpcShowPreferenceRequiredViewResponse {
}
enum RpcRenderLocation {
INLINE_VIEW_LOCATION = 0;