0.3.8 — plugins install from the catalog only (no auto-install)
Drop first-run auto-install. New plugins are discovered and installed by hand from Settings → Plugins, which now shows an 'Available to install' catalog fetched from the Gitea plugin registry (with loading / offline-retry states). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "exbyte-depot",
|
||||
"private": true,
|
||||
"version": "0.3.7",
|
||||
"version": "0.3.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}
|
||||
|
||||
47
src/App.tsx
47
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<RegistryEntry[]>([]);
|
||||
const [regBusy, setRegBusy] = useState(true);
|
||||
const [regErr, setRegErr] = useState("");
|
||||
const [installing, setInstalling] = useState<string>("");
|
||||
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 = (<>
|
||||
<div className="tm-hint">{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.")}</div>
|
||||
<div className="tm-hint">{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.")}</div>
|
||||
<div className="pl-list">
|
||||
{busy ? <div className="ur-empty"><span className="ldr" /> {t("Loading…")}</div>
|
||||
: list.length === 0 ? <div className="ur-empty">{I.hex}{t("No plugins installed yet.\nInstall one from a folder, or drop it into the plugins folder below.")}</div>
|
||||
@ -3320,21 +3319,23 @@ function PluginsModal({ onClose, onReload, onFlash, embedded }: { onClose?: () =
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{reg.filter((e) => !list.some((p) => p.id === e.id)).length > 0 && (<>
|
||||
<div className="pl-official-h">{t("Official plugins")}</div>
|
||||
{reg.filter((e) => !list.some((p) => p.id === e.id)).map((e) => (
|
||||
<div key={e.id} className="pl-card">
|
||||
<div className="pl-main">
|
||||
<div className="pl-top"><b>{e.name}</b><span className="pl-ver">v{e.version}</span></div>
|
||||
{e.description && <div className="pl-desc">{e.description}</div>}
|
||||
<div className="pl-meta">{e.author && <span className="pl-tag">{e.author}</span>}{(e.permissions || []).map((perm) => <span key={perm} className="pl-perm">{perm}</span>)}</div>
|
||||
<div className="pl-official-h">{t("Available to install")}<span className="pl-cat-sub">{t("from the Exbyte plugin registry")}</span></div>
|
||||
{regBusy ? <div className="ur-empty"><span className="ldr" /> {t("Loading the plugin catalog…")}</div>
|
||||
: regErr ? <div className="ur-empty">{I.hex}{t("Couldn't reach the plugin registry.")}<button className="mbtn ghost" style={{ flex: "none", padding: "6px 12px", marginTop: 8 }} onClick={loadReg}>{I.sync}{t("Retry")}</button></div>
|
||||
: reg.filter((e) => !list.some((p) => p.id === e.id)).length === 0
|
||||
? <div className="ur-empty">{I.check}{t("Everything from the catalog is already installed.")}</div>
|
||||
: reg.filter((e) => !list.some((p) => p.id === e.id)).map((e) => (
|
||||
<div key={e.id} className="pl-card">
|
||||
<div className="pl-main">
|
||||
<div className="pl-top"><b>{e.name}</b><span className="pl-ver">v{e.version}</span></div>
|
||||
{e.description && <div className="pl-desc">{e.description}</div>}
|
||||
<div className="pl-meta">{e.author && <span className="pl-tag">{e.author}</span>}{(e.permissions || []).map((perm) => <span key={perm} className="pl-perm">{perm}</span>)}</div>
|
||||
</div>
|
||||
<div className="pl-actions">
|
||||
<button className="mbtn primary" style={{ flex: "none", padding: "7px 13px", gap: 6 }} disabled={installing === e.id} onClick={() => installReg(e)}>{installing === e.id ? <span className="ldr sm" /> : I.down}{t("Install")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pl-actions">
|
||||
<button className="mbtn primary" style={{ flex: "none", padding: "7px 13px", gap: 6 }} disabled={installing === e.id} onClick={() => installReg(e)}>{installing === e.id ? <span className="ldr sm" /> : I.down}{t("Install")}</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>)}
|
||||
))}
|
||||
</div>
|
||||
<div className="modal-actions" style={{ padding: "12px 16px", borderTop: "1px solid var(--border-soft)" }}>
|
||||
<button className="mbtn ghost" onClick={() => p4.openPluginsDir().catch(() => {})} title={dir}>{I.reveal}{t("Open plugins folder")}</button>
|
||||
|
||||
@ -187,11 +187,17 @@ const D: Record<string, Tr> = {
|
||||
"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" },
|
||||
|
||||
Reference in New Issue
Block a user