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:
Hakan Seven 2026-07-29 16:28:20 +03:00
commit fcc6e1f8e1
9 changed files with 92 additions and 48 deletions

View file

@ -81,9 +81,10 @@ on the web.
Marketplace plugins are native `.dll`, `.so`, or `.dylib` packages launched in
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
its commands. Installed desktop plugins remain in the per-user plugins folder
and load again on the next desktop launch.
plugin runner. The web Plugin button and `PLUGINS` command therefore show a
short explanation with a link to download the desktop app instead of opening
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`)
- `open_url`: `open::that` on native, `window.open(_blank)` on web.

View file

@ -334,8 +334,9 @@ plugin dead, and reports a `CallTimeout` error via the normal plugin error path.
## Marketplace
The desktop **Plugin Manager** (`PLUGINS` / `PLUGINMANAGER`, or the Start-page
button) installs plugins from GitHub Releases. It is hidden in the browser
build because marketplace packages are native dynamic libraries:
button) installs plugins from GitHub Releases. In the browser, the same entry
points show a desktop-download notice because marketplace packages are native
dynamic libraries:
- **Curated registry** — [`plugins/registry.json`](../plugins/registry.json) in
this repo lists discoverable plugins. The host fetches it from `main` at

View file

@ -236,6 +236,8 @@ pub fn start_allowed(cmd: &str) -> bool {
| "REPORT"
| "CHANGELOG"
| "ABOUT"
| "PLUGINS"
| "PLUGINMANAGER"
| "DONATE"
| "WEBVERSION"
| "HELP"
@ -245,8 +247,6 @@ pub fn start_allowed(cmd: &str) -> bool {
| "CUILOAD"
| "CUIIMPORT"
)
|| (!cfg!(target_arch = "wasm32")
&& matches!(cmd, "PLUGINS" | "PLUGINMANAGER"))
}
// ── Autocomplete registry — one-shot commands ──────────────────────────────

View file

@ -212,17 +212,10 @@ impl OpenCADStudio {
return Some(Task::done(Message::AboutOpen));
}
#[cfg(not(target_arch = "wasm32"))]
"PLUGINS" | "PLUGINMANAGER" => {
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" => {
crate::sys::open_url("https://github.com/HakanSeven12/OpenCADStudio/releases");
self.command_line.push_info("Opening release notes...");

View file

@ -521,6 +521,7 @@ pub(super) struct OpenCADStudio {
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
/// Ids of external packages actually loaded this session (a subset of
/// `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>,
/// Curated plugin registry fetched from the OpenCADStudio repo.
plugin_registry: Vec<crate::plugin::external::RegistryEntry>,
@ -1313,7 +1314,6 @@ impl ClipboardDeps {
pub enum ModalKind {
About,
Shortcuts,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
PluginManager,
UpdateNotice,
Layers,

View file

@ -4058,8 +4058,7 @@ impl OpenCADStudio {
Message::PluginManagerOpen => {
#[cfg(target_arch = "wasm32")]
{
self.command_line
.push_info("External plugins are available in the desktop app.");
self.active_modal = Some(super::ModalKind::PluginManager);
return Task::none();
}
#[cfg(not(target_arch = "wasm32"))]

View file

@ -2300,11 +2300,11 @@ pub(super) fn start_page_view<'a>(
.into(),
outline_btn("Options", Message::OptionsOpen).into(),
];
// External plugins are native dynamic libraries and the web build is
// already in the browser, so both actions are desktop-only.
secondary_items.push(outline_btn("Plugins", Message::PluginManagerOpen).into());
// The web build is already in the browser, so only the desktop offers a
// link to the web version.
#[cfg(not(target_arch = "wasm32"))]
{
secondary_items.push(outline_btn("Plugins", Message::PluginManagerOpen).into());
// Filled with the active theme's primary colour.
secondary_items.push(
button(text("OCS Web").size(14))

View file

@ -71,7 +71,10 @@ impl OpenCADStudio {
520,
500,
),
super::super::ModalKind::PluginManager => sized(
super::super::ModalKind::PluginManager => {
#[cfg(not(target_arch = "wasm32"))]
{
sized(
crate::ui::window::plugin_manager::view_window(
&self.disabled_plugins,
&self.external_plugins,
@ -80,7 +83,8 @@ impl OpenCADStudio {
registry: &self.plugin_registry,
registry_loading: self.plugin_registry_loading,
registry_error: self.plugin_registry_error.as_deref(),
registry_error_details_open: self.plugin_registry_error_details_open,
registry_error_details_open: self
.plugin_registry_error_details_open,
input: &self.plugin_repo_input,
search: &self.plugin_search_input,
repos: &self.plugin_repos,
@ -95,7 +99,17 @@ impl OpenCADStudio {
),
940,
600,
),
)
}
#[cfg(target_arch = "wasm32")]
{
sized(
crate::ui::window::plugin_manager::view_web_notice(),
520,
260,
)
}
}
super::super::ModalKind::UpdateNotice => {
let latest = self.update_notice_version.as_deref().unwrap_or("?");
let body = self.update_notice_body.as_deref().unwrap_or("");

View file

@ -4,6 +4,8 @@
//! launches). Dynamic loading still comes with the phase-2 loader; see
//! `docs/plugin-architecture.md`.
#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
use crate::app::Message;
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
use iced::widget::{
@ -33,9 +35,13 @@ pub struct MarketView<'a> {
pub status: &'a str,
}
// External plugins are native dynamic libraries, so the browser build must not
// advertise a manager it cannot use.
#[cfg(not(target_arch = "wasm32"))]
/// Latest desktop builds, used by the browser-only plugin notice.
#[cfg(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 {
names: &["PLUGINS", "PLUGINMANAGER"]
});
@ -844,6 +850,36 @@ pub fn view_window<'a>(
.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)]
mod tests {
use super::{newest_update, registry_error_message, repository_display_name};