fix(web): link plugins to desktop app
Keep browser entry points visible while explaining that native plugins require a desktop install. Refs #567
This commit is contained in:
parent
2424eeb74d
commit
fcc6e1f8e1
9 changed files with 92 additions and 48 deletions
|
|
@ -81,9 +81,10 @@ on the web.
|
||||||
|
|
||||||
Marketplace plugins are native `.dll`, `.so`, or `.dylib` packages launched in
|
Marketplace plugins are native `.dll`, `.so`, or `.dylib` packages launched in
|
||||||
an isolated child process. Browsers cannot load those libraries or spawn the
|
an isolated child process. Browsers cannot load those libraries or spawn the
|
||||||
plugin runner, so the web build does not show the Plugin Manager or register
|
plugin runner. The web Plugin button and `PLUGINS` command therefore show a
|
||||||
its commands. Installed desktop plugins remain in the per-user plugins folder
|
short explanation with a link to download the desktop app instead of opening
|
||||||
and load again on the next desktop launch.
|
the marketplace. Installed desktop plugins remain in the per-user plugins
|
||||||
|
folder and load again on the next desktop launch.
|
||||||
|
|
||||||
### Platform shims (`src/sys.rs`)
|
### Platform shims (`src/sys.rs`)
|
||||||
- `open_url`: `open::that` on native, `window.open(_blank)` on web.
|
- `open_url`: `open::that` on native, `window.open(_blank)` on web.
|
||||||
|
|
|
||||||
|
|
@ -334,8 +334,9 @@ plugin dead, and reports a `CallTimeout` error via the normal plugin error path.
|
||||||
## Marketplace
|
## Marketplace
|
||||||
|
|
||||||
The desktop **Plugin Manager** (`PLUGINS` / `PLUGINMANAGER`, or the Start-page
|
The desktop **Plugin Manager** (`PLUGINS` / `PLUGINMANAGER`, or the Start-page
|
||||||
button) installs plugins from GitHub Releases. It is hidden in the browser
|
button) installs plugins from GitHub Releases. In the browser, the same entry
|
||||||
build because marketplace packages are native dynamic libraries:
|
points show a desktop-download notice because marketplace packages are native
|
||||||
|
dynamic libraries:
|
||||||
|
|
||||||
- **Curated registry** — [`plugins/registry.json`](../plugins/registry.json) in
|
- **Curated registry** — [`plugins/registry.json`](../plugins/registry.json) in
|
||||||
this repo lists discoverable plugins. The host fetches it from `main` at
|
this repo lists discoverable plugins. The host fetches it from `main` at
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,8 @@ pub fn start_allowed(cmd: &str) -> bool {
|
||||||
| "REPORT"
|
| "REPORT"
|
||||||
| "CHANGELOG"
|
| "CHANGELOG"
|
||||||
| "ABOUT"
|
| "ABOUT"
|
||||||
|
| "PLUGINS"
|
||||||
|
| "PLUGINMANAGER"
|
||||||
| "DONATE"
|
| "DONATE"
|
||||||
| "WEBVERSION"
|
| "WEBVERSION"
|
||||||
| "HELP"
|
| "HELP"
|
||||||
|
|
@ -245,8 +247,6 @@ pub fn start_allowed(cmd: &str) -> bool {
|
||||||
| "CUILOAD"
|
| "CUILOAD"
|
||||||
| "CUIIMPORT"
|
| "CUIIMPORT"
|
||||||
)
|
)
|
||||||
|| (!cfg!(target_arch = "wasm32")
|
|
||||||
&& matches!(cmd, "PLUGINS" | "PLUGINMANAGER"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Autocomplete registry — one-shot commands ──────────────────────────────
|
// ── Autocomplete registry — one-shot commands ──────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -212,17 +212,10 @@ impl OpenCADStudio {
|
||||||
return Some(Task::done(Message::AboutOpen));
|
return Some(Task::done(Message::AboutOpen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
"PLUGINS" | "PLUGINMANAGER" => {
|
"PLUGINS" | "PLUGINMANAGER" => {
|
||||||
return Some(Task::done(Message::PluginManagerOpen));
|
return Some(Task::done(Message::PluginManagerOpen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
"PLUGINS" | "PLUGINMANAGER" => {
|
|
||||||
self.command_line
|
|
||||||
.push_info("External plugins are available in the desktop app.");
|
|
||||||
}
|
|
||||||
|
|
||||||
"CHANGELOG" => {
|
"CHANGELOG" => {
|
||||||
crate::sys::open_url("https://github.com/HakanSeven12/OpenCADStudio/releases");
|
crate::sys::open_url("https://github.com/HakanSeven12/OpenCADStudio/releases");
|
||||||
self.command_line.push_info("Opening release notes...");
|
self.command_line.push_info("Opening release notes...");
|
||||||
|
|
|
||||||
|
|
@ -521,6 +521,7 @@ pub(super) struct OpenCADStudio {
|
||||||
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
||||||
/// Ids of external packages actually loaded this session (a subset of
|
/// Ids of external packages actually loaded this session (a subset of
|
||||||
/// `external_plugins` — compatible, with a library, dlopen'd at startup).
|
/// `external_plugins` — compatible, with a library, dlopen'd at startup).
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||||
loaded_plugin_ids: rustc_hash::FxHashSet<String>,
|
loaded_plugin_ids: rustc_hash::FxHashSet<String>,
|
||||||
/// Curated plugin registry fetched from the OpenCADStudio repo.
|
/// Curated plugin registry fetched from the OpenCADStudio repo.
|
||||||
plugin_registry: Vec<crate::plugin::external::RegistryEntry>,
|
plugin_registry: Vec<crate::plugin::external::RegistryEntry>,
|
||||||
|
|
@ -1313,7 +1314,6 @@ impl ClipboardDeps {
|
||||||
pub enum ModalKind {
|
pub enum ModalKind {
|
||||||
About,
|
About,
|
||||||
Shortcuts,
|
Shortcuts,
|
||||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
|
||||||
PluginManager,
|
PluginManager,
|
||||||
UpdateNotice,
|
UpdateNotice,
|
||||||
Layers,
|
Layers,
|
||||||
|
|
|
||||||
|
|
@ -4058,8 +4058,7 @@ impl OpenCADStudio {
|
||||||
Message::PluginManagerOpen => {
|
Message::PluginManagerOpen => {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
self.command_line
|
self.active_modal = Some(super::ModalKind::PluginManager);
|
||||||
.push_info("External plugins are available in the desktop app.");
|
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
||||||
|
|
@ -2300,11 +2300,11 @@ pub(super) fn start_page_view<'a>(
|
||||||
.into(),
|
.into(),
|
||||||
outline_btn("Options", Message::OptionsOpen).into(),
|
outline_btn("Options", Message::OptionsOpen).into(),
|
||||||
];
|
];
|
||||||
// External plugins are native dynamic libraries and the web build is
|
secondary_items.push(outline_btn("Plugins", Message::PluginManagerOpen).into());
|
||||||
// already in the browser, so both actions are desktop-only.
|
// The web build is already in the browser, so only the desktop offers a
|
||||||
|
// link to the web version.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
secondary_items.push(outline_btn("Plugins", Message::PluginManagerOpen).into());
|
|
||||||
// Filled with the active theme's primary colour.
|
// Filled with the active theme's primary colour.
|
||||||
secondary_items.push(
|
secondary_items.push(
|
||||||
button(text("OCS Web").size(14))
|
button(text("OCS Web").size(14))
|
||||||
|
|
|
||||||
|
|
@ -71,31 +71,45 @@ impl OpenCADStudio {
|
||||||
520,
|
520,
|
||||||
500,
|
500,
|
||||||
),
|
),
|
||||||
super::super::ModalKind::PluginManager => sized(
|
super::super::ModalKind::PluginManager => {
|
||||||
crate::ui::window::plugin_manager::view_window(
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
&self.disabled_plugins,
|
{
|
||||||
&self.external_plugins,
|
sized(
|
||||||
&self.loaded_plugin_ids,
|
crate::ui::window::plugin_manager::view_window(
|
||||||
crate::ui::window::plugin_manager::MarketView {
|
&self.disabled_plugins,
|
||||||
registry: &self.plugin_registry,
|
&self.external_plugins,
|
||||||
registry_loading: self.plugin_registry_loading,
|
&self.loaded_plugin_ids,
|
||||||
registry_error: self.plugin_registry_error.as_deref(),
|
crate::ui::window::plugin_manager::MarketView {
|
||||||
registry_error_details_open: self.plugin_registry_error_details_open,
|
registry: &self.plugin_registry,
|
||||||
input: &self.plugin_repo_input,
|
registry_loading: self.plugin_registry_loading,
|
||||||
search: &self.plugin_search_input,
|
registry_error: self.plugin_registry_error.as_deref(),
|
||||||
repos: &self.plugin_repos,
|
registry_error_details_open: self
|
||||||
release_tags: &self.repo_release_tags,
|
.plugin_registry_error_details_open,
|
||||||
selected_tag: &self.repo_selected_tag,
|
input: &self.plugin_repo_input,
|
||||||
selected_repo: self.selected_plugin_repo.as_deref(),
|
search: &self.plugin_search_input,
|
||||||
readmes: &self.plugin_readmes,
|
repos: &self.plugin_repos,
|
||||||
readme_loading: &self.plugin_readme_loading,
|
release_tags: &self.repo_release_tags,
|
||||||
status: &self.marketplace_status,
|
selected_tag: &self.repo_selected_tag,
|
||||||
},
|
selected_repo: self.selected_plugin_repo.as_deref(),
|
||||||
&self.active_theme,
|
readmes: &self.plugin_readmes,
|
||||||
),
|
readme_loading: &self.plugin_readme_loading,
|
||||||
940,
|
status: &self.marketplace_status,
|
||||||
600,
|
},
|
||||||
),
|
&self.active_theme,
|
||||||
|
),
|
||||||
|
940,
|
||||||
|
600,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
sized(
|
||||||
|
crate::ui::window::plugin_manager::view_web_notice(),
|
||||||
|
520,
|
||||||
|
260,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
super::super::ModalKind::UpdateNotice => {
|
super::super::ModalKind::UpdateNotice => {
|
||||||
let latest = self.update_notice_version.as_deref().unwrap_or("?");
|
let latest = self.update_notice_version.as_deref().unwrap_or("?");
|
||||||
let body = self.update_notice_body.as_deref().unwrap_or("");
|
let body = self.update_notice_body.as_deref().unwrap_or("");
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
//! launches). Dynamic loading still comes with the phase-2 loader; see
|
//! launches). Dynamic loading still comes with the phase-2 loader; see
|
||||||
//! `docs/plugin-architecture.md`.
|
//! `docs/plugin-architecture.md`.
|
||||||
|
|
||||||
|
#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
|
|
@ -33,9 +35,13 @@ pub struct MarketView<'a> {
|
||||||
pub status: &'a str,
|
pub status: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
// External plugins are native dynamic libraries, so the browser build must not
|
/// Latest desktop builds, used by the browser-only plugin notice.
|
||||||
// advertise a manager it cannot use.
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
pub const DESKTOP_DOWNLOAD_URL: &str =
|
||||||
|
"https://github.com/HakanSeven12/OpenCADStudio/releases/latest";
|
||||||
|
|
||||||
|
// Register the command names for autocomplete. On the web they open a desktop
|
||||||
|
// download notice instead of the native marketplace.
|
||||||
inventory::submit!(crate::command::CommandRegistration {
|
inventory::submit!(crate::command::CommandRegistration {
|
||||||
names: &["PLUGINS", "PLUGINMANAGER"]
|
names: &["PLUGINS", "PLUGINMANAGER"]
|
||||||
});
|
});
|
||||||
|
|
@ -844,6 +850,36 @@ pub fn view_window<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Browser replacement for the native Plugin Manager.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub fn view_web_notice<'a>() -> Element<'a, Message> {
|
||||||
|
let download = button(text("Download desktop app").size(13))
|
||||||
|
.on_press(Message::OpenUrl(DESKTOP_DOWNLOAD_URL.to_string()))
|
||||||
|
.padding([8, 16])
|
||||||
|
.style(button::primary);
|
||||||
|
|
||||||
|
container(
|
||||||
|
column![
|
||||||
|
text("Plugins require the desktop app").size(20),
|
||||||
|
text(
|
||||||
|
"Open CAD Studio plugins are native packages and cannot run inside a browser. \
|
||||||
|
Install the desktop app to browse, install, and use plugins.",
|
||||||
|
)
|
||||||
|
.size(13)
|
||||||
|
.style(muted_style),
|
||||||
|
Space::new().height(8),
|
||||||
|
download,
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.padding(24)
|
||||||
|
.width(Fill),
|
||||||
|
)
|
||||||
|
.center(Fill)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{newest_update, registry_error_message, repository_display_name};
|
use super::{newest_update, registry_error_message, repository_display_name};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue