diff --git a/package.json b/package.json index 2f63786..5006242 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "exbyte-depot", "private": true, - "version": "0.3.7", + "version": "0.3.8", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d44b7a0..f0b094e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "exbyte-depot" -version = "0.3.7" +version = "0.3.8" description = "Exbyte Depot — native Perforce client by Exbyte Studios" authors = ["Exbyte Studios"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e3dea56..b432be8 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Exbyte Depot", "mainBinaryName": "Exbyte Depot", - "version": "0.3.7", + "version": "0.3.8", "identifier": "com.bonchellon.exbyte-depot", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.css b/src/App.css index 93f1dba..1330339 100644 --- a/src/App.css +++ b/src/App.css @@ -731,7 +731,8 @@ body.resizing-v{cursor:row-resize!important;user-select:none} .th-token input[type=color]::-webkit-color-swatch-wrapper{padding:0} /* plugins manager */ .pl-list{flex:1;min-height:0;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:10px} -.pl-official-h{font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);font-weight:700;margin:8px 2px 0;padding-top:10px;border-top:1px solid var(--border-soft)} +.pl-official-h{font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);font-weight:700;margin:8px 2px 0;padding-top:10px;border-top:1px solid var(--border-soft);display:flex;align-items:baseline;gap:8px} +.pl-cat-sub{text-transform:none;letter-spacing:0;font-weight:500;color:var(--faint);opacity:.8} .pl-card{display:flex;gap:12px;align-items:flex-start;border:1px solid var(--border);border-radius:12px;padding:13px 15px;background:var(--panel);transition:.15s} .pl-card.off{opacity:.55} .pl-main{flex:1;min-width:0} diff --git a/src/App.tsx b/src/App.tsx index bd3e335..a539ee6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import "./App.css"; import { p4, statusOf, splitPath, kindOf, isCodeFile, fmtTime, describeFiles, filelogRevs, humanSize, leaf, saveSession, loadSession, clearSession, saveScope, loadScope, recentScopes, pushRecentScope, fileBytes, getEditor, setEditorPref, P4Info, OpenedFile, Client, Change, Session, Describe, Dir, User, Editor, FileRev, FsEntry, PluginInfo } from "./p4"; import { t, LANG, LANGS, setLangGlobal, Lang } from "./i18n"; import { Theme, THEME_TOKENS, BUILTIN_THEMES, allThemes, loadCustomThemes, saveCustomThemes, loadActiveId, saveActiveId, resolveTheme, applyTheme, tokenValue, newCustomId, exportTheme, importTheme } from "./themes"; -import { loadPlugins, getRegistry, subscribeRegistry, runSubmitHooks, PluginView, PluginDock, autoInstallOfficial, fetchRegistry, installFromRegistry, RegistryEntry } from "./plugins"; +import { loadPlugins, getRegistry, subscribeRegistry, runSubmitHooks, PluginView, PluginDock, fetchRegistry, installFromRegistry, RegistryEntry } from "./plugins"; import { aiSummary, aiExplainCode, getExplain, saveExplain, dropExplain, initials, avatarColor, activity } from "./ai"; // native OS notification (works even when the window is hidden in the tray) @@ -648,12 +648,8 @@ function Workbench({ info, session, light, toggleTheme, themeId, setThemeId, lan if (pluginsLoaded.current || !info?.clientName) return; pluginsLoaded.current = true; (async () => { - // auto-install official plugins (e.g. Unreal) on first run so no one loses tooling - try { - const installed = new Set((await p4.pluginList()).map((p) => p.id)); - const added = await autoInstallOfficial(installed); - if (added.length) console.log("[plugins] auto-installed:", added); - } catch { /* offline — skip */ } + // No auto-install: only load what the user chose to install. New plugins are + // browsed and installed by hand from Settings → Plugins (the registry catalog). const { loaded, errors } = await reloadPlugins(); if (errors.length) console.warn("[plugins] load errors:", errors); if (loaded) flash(t("{n} plugin(s) loaded", { n: loaded })); @@ -3275,9 +3271,12 @@ function PluginsModal({ onClose, onReload, onFlash, embedded }: { onClose?: () = const [busy, setBusy] = useState(true); const [dir, setDir] = useState(""); const [reg, setReg] = useState([]); + const [regBusy, setRegBusy] = useState(true); + const [regErr, setRegErr] = useState(""); const [installing, setInstalling] = useState(""); const load = () => { setBusy(true); p4.pluginList().then(setList).catch((e) => onFlash(String(e), true)).finally(() => setBusy(false)); }; - useEffect(() => { load(); p4.pluginsDirPath().then(setDir).catch(() => {}); fetchRegistry().then(setReg).catch(() => {}); const k = (e: KeyboardEvent) => e.key === "Escape" && onClose?.(); document.addEventListener("keydown", k); return () => document.removeEventListener("keydown", k); }, []); // eslint-disable-line + const loadReg = () => { setRegBusy(true); setRegErr(""); fetchRegistry().then(setReg).catch((e) => setRegErr(String(e))).finally(() => setRegBusy(false)); }; + useEffect(() => { load(); loadReg(); p4.pluginsDirPath().then(setDir).catch(() => {}); const k = (e: KeyboardEvent) => e.key === "Escape" && onClose?.(); document.addEventListener("keydown", k); return () => document.removeEventListener("keydown", k); }, []); // eslint-disable-line async function installReg(entry: RegistryEntry) { setInstalling(entry.id); try { await installFromRegistry(entry); load(); await onReload(); onFlash(t("Installed plugin “{id}”.", { id: entry.name })); } @@ -3300,7 +3299,7 @@ function PluginsModal({ onClose, onReload, onFlash, embedded }: { onClose?: () = } catch (e) { onFlash(String(e), true); } } const body = (<> -
{t("Plugins add features on top of the core app. Install a folder with a plugin.json, toggle plugins on/off, then they load automatically. Plugins run in-app — only install ones you trust.")}
+
{t("Plugins add features on top of the core app. Browse the catalog below and install what you need — nothing is installed automatically. Toggle plugins on/off; enabled ones load on launch. Plugins run in-app — only install ones you trust.")}
{busy ?
{t("Loading…")}
: list.length === 0 ?
{I.hex}{t("No plugins installed yet.\nInstall one from a folder, or drop it into the plugins folder below.")}
@@ -3320,21 +3319,23 @@ function PluginsModal({ onClose, onReload, onFlash, embedded }: { onClose?: () =
))} - {reg.filter((e) => !list.some((p) => p.id === e.id)).length > 0 && (<> -
{t("Official plugins")}
- {reg.filter((e) => !list.some((p) => p.id === e.id)).map((e) => ( -
-
-
{e.name}v{e.version}
- {e.description &&
{e.description}
} -
{e.author && {e.author}}{(e.permissions || []).map((perm) => {perm})}
+
{t("Available to install")}{t("from the Exbyte plugin registry")}
+ {regBusy ?
{t("Loading the plugin catalog…")}
+ : regErr ?
{I.hex}{t("Couldn't reach the plugin registry.")}
+ : reg.filter((e) => !list.some((p) => p.id === e.id)).length === 0 + ?
{I.check}{t("Everything from the catalog is already installed.")}
+ : reg.filter((e) => !list.some((p) => p.id === e.id)).map((e) => ( +
+
+
{e.name}v{e.version}
+ {e.description &&
{e.description}
} +
{e.author && {e.author}}{(e.permissions || []).map((perm) => {perm})}
+
+
+ +
-
- -
-
- ))} - )} + ))}
diff --git a/src/i18n.ts b/src/i18n.ts index 463d423..f744bd3 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -187,11 +187,17 @@ const D: Record = { "Appearance": { ru: "Оформление", de: "Darstellung", fr: "Apparence", es: "Apariencia" }, "Base theme": { ru: "Базовая тема", de: "Basis-Theme", fr: "Thème de base", es: "Tema base" }, "{n} installed": { ru: "установлено: {n}", de: "{n} installiert", fr: "{n} installés", es: "{n} instalados" }, - "Plugins add features on top of the core app. Install a folder with a plugin.json, toggle plugins on/off, then they load automatically. Plugins run in-app — only install ones you trust.": { ru: "Плагины добавляют функции поверх ядра. Установи папку с plugin.json, включай/выключай — они грузятся автоматически. Плагины работают внутри приложения — ставь только те, которым доверяешь.", de: "Plugins fügen Funktionen zur Kern-App hinzu. Installiere einen Ordner mit plugin.json, schalte Plugins ein/aus — sie laden automatisch. Plugins laufen in der App — installiere nur vertrauenswürdige.", fr: "Les extensions ajoutent des fonctions au cœur de l’app. Installez un dossier avec plugin.json, activez/désactivez — elles se chargent automatiquement. Elles s’exécutent dans l’app — n’installez que celles de confiance.", es: "Los plugins añaden funciones al núcleo. Instala una carpeta con plugin.json, actívalos/desactívalos y se cargan solos. Se ejecutan en la app — instala solo los de confianza." }, + "Plugins add features on top of the core app. Browse the catalog below and install what you need — nothing is installed automatically. Toggle plugins on/off; enabled ones load on launch. Plugins run in-app — only install ones you trust.": { ru: "Плагины добавляют функции поверх ядра. Смотри каталог ниже и ставь нужное — ничего не ставится само. Включай/выключай; включённые грузятся при запуске. Плагины работают внутри приложения — ставь только те, которым доверяешь.", de: "Plugins erweitern die Kern-App. Durchsuche den Katalog unten und installiere, was du brauchst — nichts wird automatisch installiert. Plugins ein/aus schalten; aktivierte laden beim Start. Sie laufen in der App — installiere nur vertrauenswürdige.", fr: "Les extensions enrichissent le cœur de l’app. Parcourez le catalogue ci-dessous et installez ce qu’il faut — rien n’est installé automatiquement. Activez/désactivez ; les activées se chargent au démarrage. Elles s’exécutent dans l’app — n’installez que celles de confiance.", es: "Los plugins añaden funciones al núcleo. Explora el catálogo abajo e instala lo que necesites — nada se instala solo. Actívalos/desactívalos; los activos se cargan al iniciar. Se ejecutan en la app — instala solo los de confianza." }, "No plugins installed yet.\nInstall one from a folder, or drop it into the plugins folder below.": { ru: "Плагинов пока нет.\nУстанови из папки или положи в папку плагинов ниже.", de: "Noch keine Plugins.\nAus einem Ordner installieren oder in den Plugin-Ordner unten legen.", fr: "Aucune extension.\nInstallez depuis un dossier ou déposez-la dans le dossier ci-dessous.", es: "Aún no hay plugins.\nInstala desde una carpeta o suéltalo en la carpeta de abajo." }, "Enable": { ru: "Включить", de: "Aktivieren", fr: "Activer", es: "Activar" }, "Disable": { ru: "Выключить", de: "Deaktivieren", fr: "Désactiver", es: "Desactivar" }, "Official plugins": { ru: "Официальные плагины", de: "Offizielle Plugins", fr: "Extensions officielles", es: "Plugins oficiales" }, + "Available to install": { ru: "Доступно для установки", de: "Verfügbar zur Installation", fr: "Disponibles à l’installation", es: "Disponibles para instalar" }, + "from the Exbyte plugin registry": { ru: "из реестра плагинов Exbyte", de: "aus der Exbyte-Plugin-Registry", fr: "depuis le registre de plugins Exbyte", es: "del registro de plugins de Exbyte" }, + "Loading the plugin catalog…": { ru: "Загружаю каталог плагинов…", de: "Plugin-Katalog wird geladen…", fr: "Chargement du catalogue…", es: "Cargando el catálogo…" }, + "Couldn't reach the plugin registry.": { ru: "Не удалось получить реестр плагинов.", de: "Plugin-Registry nicht erreichbar.", fr: "Registre de plugins injoignable.", es: "No se pudo acceder al registro." }, + "Retry": { ru: "Повторить", de: "Erneut", fr: "Réessayer", es: "Reintentar" }, + "Everything from the catalog is already installed.": { ru: "Всё из каталога уже установлено.", de: "Alles aus dem Katalog ist bereits installiert.", fr: "Tout le catalogue est déjà installé.", es: "Todo el catálogo ya está instalado." }, "Install": { ru: "Установить", de: "Installieren", fr: "Installer", es: "Instalar" }, "Open plugins folder": { ru: "Открыть папку плагинов", de: "Plugin-Ordner öffnen", fr: "Ouvrir le dossier des extensions", es: "Abrir carpeta de plugins" }, "Reload": { ru: "Перезагрузить", de: "Neu laden", fr: "Recharger", es: "Recargar" },