diff --git a/docs/native-vs-web.md b/docs/native-vs-web.md index 41b48fb1c..631608b45 100644 --- a/docs/native-vs-web.md +++ b/docs/native-vs-web.md @@ -18,6 +18,7 @@ browser can't provide. This page lists the differences. | Parallelism | Multi-threaded (rayon) | Single-threaded | | GPU backend | Vulkan / DX12 / Metal (wgpu) | WebGL2 (WebGPU where available) | | Update check / external links | `ureq`, `open` | skipped / `window.open` | +| External plugins | Native packages + Plugin Manager | **No** — native libraries cannot run in a browser | ## Details @@ -76,6 +77,14 @@ native and sequential `std` iterators on wasm, so the same call sites run in parallel on the desktop and serially in the browser. Large drawings are slower on the web. +### External plugins — web: unavailable + +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. + ### Platform shims (`src/sys.rs`) - `open_url`: `open::that` on native, `window.open(_blank)` on web. - `download_bytes`: web only (Blob + anchor download). diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index 842957874..7d6c08ca1 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -333,8 +333,9 @@ plugin dead, and reports a `CallTimeout` error via the normal plugin error path. ## Marketplace -The **Plugin Manager** (`PLUGINS` / `PLUGINMANAGER`, or the Start-page button) -installs plugins from GitHub Releases: +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: - **Curated registry** — [`plugins/registry.json`](../plugins/registry.json) in this repo lists discoverable plugins. The host fetches it from `main` at @@ -343,7 +344,7 @@ installs plugins from GitHub Releases: [`plugins/README.md`](../plugins/README.md)); merged PRs reach every user with no app update. - **Manual link** — *Add a repository* (`owner/repo`) for unlisted or private - dev repos; linked repos persist in `settings.txt` (`plugin_repos=`). + dev repos; linked repos persist in `settings.json`. - **Install / upgrade / reinstall** — pick a release from the dropdown and *Install*; the host downloads the platform asset + `plugin.toml` into the plugins folder, checking `api_version` first. Reinstalling overwrites and diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index a6e81750a..c9f28b3b7 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -236,8 +236,6 @@ pub fn start_allowed(cmd: &str) -> bool { | "REPORT" | "CHANGELOG" | "ABOUT" - | "PLUGINS" - | "PLUGINMANAGER" | "DONATE" | "WEBVERSION" | "HELP" @@ -247,6 +245,8 @@ pub fn start_allowed(cmd: &str) -> bool { | "CUILOAD" | "CUIIMPORT" ) + || (!cfg!(target_arch = "wasm32") + && matches!(cmd, "PLUGINS" | "PLUGINMANAGER")) } // ── Autocomplete registry — one-shot commands ────────────────────────────── diff --git a/src/app/commands/view.rs b/src/app/commands/view.rs index d96f4125e..d4a1e34d0 100644 --- a/src/app/commands/view.rs +++ b/src/app/commands/view.rs @@ -212,10 +212,17 @@ 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..."); diff --git a/src/app/mod.rs b/src/app/mod.rs index 3f41a0018..c827c07ad 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1313,6 +1313,7 @@ impl ClipboardDeps { pub enum ModalKind { About, Shortcuts, + #[cfg_attr(target_arch = "wasm32", allow(dead_code))] PluginManager, UpdateNotice, Layers, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 6d96b31a6..96f0fdf96 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -291,8 +291,11 @@ impl OpenCADStudio { } #[cfg(target_arch = "wasm32")] - pub(in crate::app) fn fetch_releases_task(&self, _repo: String) -> Task { - Task::none() + pub(in crate::app) fn fetch_releases_task(&self, repo: String) -> Task { + Task::done(Message::PluginReleasesFetched( + repo, + Err("External plugins are available in the desktop app.".to_string()), + )) } /// Background task: fetch a repository README from its default branch. @@ -306,8 +309,11 @@ impl OpenCADStudio { } #[cfg(target_arch = "wasm32")] - pub(in crate::app) fn fetch_plugin_readme_task(&self, _repo: String) -> Task { - Task::none() + pub(in crate::app) fn fetch_plugin_readme_task(&self, repo: String) -> Task { + Task::done(Message::PluginReadmeFetched( + repo, + Err("Plugin details are available in the desktop app.".to_string()), + )) } /// Background task: download and install the `tag` release of `owner/repo`. @@ -328,7 +334,9 @@ impl OpenCADStudio { #[cfg(target_arch = "wasm32")] pub(in crate::app) fn install_task(&self, _repo: String, _tag: String) -> Task { - Task::none() + Task::done(Message::PluginInstalled(Err( + "External plugins are available in the desktop app.".to_string(), + ))) } /// Gather the full persisted config (all sections) from live app state. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 333bfb308..876304cf3 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -4056,14 +4056,20 @@ impl OpenCADStudio { // ── Plugin Manager window ───────────────────────────────────── Message::PluginManagerOpen => { - // Refresh the on-disk external-plugin list each time the manager - // opens so newly dropped-in packages show up. - self.external_plugins = crate::plugin::external::discover(); - self.marketplace_status.clear(); - self.active_modal = Some(super::ModalKind::PluginManager); - // Fetch the curated registry and release lists for linked repos. + #[cfg(target_arch = "wasm32")] + { + self.command_line + .push_info("External plugins are available in the desktop app."); + return Task::none(); + } #[cfg(not(target_arch = "wasm32"))] { + // Refresh the on-disk external-plugin list each time the manager + // opens so newly dropped-in packages show up. + self.external_plugins = crate::plugin::external::discover(); + self.marketplace_status.clear(); + self.active_modal = Some(super::ModalKind::PluginManager); + // Fetch the curated registry and release lists for linked repos. self.plugin_registry_loading = true; self.plugin_registry_error = None; self.plugin_registry_error_details_open = false; @@ -4100,8 +4106,6 @@ impl OpenCADStudio { } return Task::batch(tasks); } - #[cfg(target_arch = "wasm32")] - Task::none() } Message::PluginManagerClose => { self.close_active_modal(); diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index e605ebae0..aca0b0ac9 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -2299,12 +2299,12 @@ pub(super) fn start_page_view<'a>( ) .into(), outline_btn("Options", Message::OptionsOpen).into(), - 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. + // External plugins are native dynamic libraries and the web build is + // already in the browser, so both actions are desktop-only. #[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)) diff --git a/src/plugin/external.rs b/src/plugin/external.rs index 4e4e36000..0bdd81f8a 100644 --- a/src/plugin/external.rs +++ b/src/plugin/external.rs @@ -13,6 +13,8 @@ //! .so | .dll | .dylib> //! ``` +#![cfg_attr(target_arch = "wasm32", allow(dead_code))] + use std::path::PathBuf; /// One entry in the curated plugin registry (`plugins/registry.json`). diff --git a/src/ui/window/plugin_manager.rs b/src/ui/window/plugin_manager.rs index cc3d617fe..0b95f620e 100644 --- a/src/ui/window/plugin_manager.rs +++ b/src/ui/window/plugin_manager.rs @@ -33,7 +33,9 @@ pub struct MarketView<'a> { pub status: &'a str, } -// Register the command names for autocomplete. +// External plugins are native dynamic libraries, so the browser build must not +// advertise a manager it cannot use. +#[cfg(not(target_arch = "wasm32"))] inventory::submit!(crate::command::CommandRegistration { names: &["PLUGINS", "PLUGINMANAGER"] });