0.3.6 — plugin registry, Unreal extracted to a plugin, in-app update check + fixes

- Official plugin registry hosted on Gitea; client fetches registry.json over
  HTTPS and installs plugins via backend plugin_write_file.
- Auto-install autoInstall plugins on first connect; per-machine enabled state.
- Extract all Unreal tooling (Build Solution / Unreal Log) into a separate
  "unreal" plugin (defaultEnabled) driven through the new host.ue bridge;
  core UI no longer hard-wires Unreal.
- Settings → Plugins: "Official plugins" section with Install buttons.
- Status bar: "Updates" button (right of Log) triggers an update check without
  restarting the client.
- Fixes: NEW badge hard-right/centered without shifting text; History commit
  size no longer flickers (cached per change); giant icons in empty plugin/
  People lists; Get Latest hover contrast.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-09 15:13:02 +03:00
parent f0fc2f687a
commit 8e7908ae09
13 changed files with 283 additions and 72 deletions

View File

@ -2923,7 +2923,10 @@ async fn plugin_list() -> Result<Vec<Value>, String> {
let Ok(m) = serde_json::from_str::<Value>(&txt) else { continue };
let id = m.get("id").and_then(|v| v.as_str()).map(String::from)
.unwrap_or_else(|| e.file_name().to_string_lossy().to_string());
let enabled = *en.get(&id).unwrap_or(&true);
// off by default unless the manifest opts in (defaultEnabled) — official
// plugins like Unreal ship on; the user's choice persists in enabled.json
let default_on = m.get("defaultEnabled").and_then(|v| v.as_bool()).unwrap_or(false);
let enabled = *en.get(&id).unwrap_or(&default_on);
out.push(serde_json::json!({
"id": id,
"name": m.get("name").and_then(|v| v.as_str()).unwrap_or(""),
@ -2967,6 +2970,23 @@ async fn plugin_set_enabled(id: String, enabled: bool) -> Result<(), String> {
Ok(())
}
/// Write one file of a plugin into <config>/plugins/<id>/<name>. The frontend
/// fetches the file bytes from the registry over HTTPS and hands the content here;
/// this only touches the confined plugins folder (id + name are path-validated).
#[tauri::command]
async fn plugin_write_file(id: String, name: String, content: String) -> Result<(), String> {
if id.is_empty() || !id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.') || id.contains("..") {
return Err("Invalid plugin id".into());
}
if name.is_empty() || name.contains('/') || name.contains('\\') || name.contains("..") {
return Err("Invalid file name".into());
}
let dir = plugins_dir().ok_or("No config dir")?.join(&id);
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
std::fs::write(dir.join(&name), content).map_err(|e| e.to_string())?;
Ok(())
}
fn copy_dir_recursive(from: &std::path::Path, to: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(to)?;
for e in std::fs::read_dir(from)? {
@ -3125,6 +3145,7 @@ pub fn run() {
plugin_list,
plugin_read_entry,
plugin_set_enabled,
plugin_write_file,
plugin_install,
plugin_remove,
plugins_dir_path,