diff --git a/packages/app/e2e/browser/plugin-settings.spec.ts b/packages/app/e2e/browser/plugin-settings.spec.ts index 29f42988ab..6235cd647f 100644 --- a/packages/app/e2e/browser/plugin-settings.spec.ts +++ b/packages/app/e2e/browser/plugin-settings.spec.ts @@ -14,6 +14,7 @@ import { expectConflictPreservesDraft, discardTitle, returnAndReopenSettings, + returnToPluginsPage, expectSettingsUnavailable, } from "../support/helpers/plugin-settings"; @@ -43,6 +44,11 @@ test("another client's save preserves a conflicting draft", async ({ page }) => await discardTitle(page); }); +test("Back to plugins returns from a plugin settings screen", async ({ page }) => { + await openDisplaySettings(page); + await returnToPluginsPage(page); +}); + test.describe("compact plugin settings", () => { test.use({ viewport: { width: 390, height: 844 } }); diff --git a/packages/app/e2e/support/helpers/plugin-settings.ts b/packages/app/e2e/support/helpers/plugin-settings.ts index 94143f31a2..32f07fcdfd 100644 --- a/packages/app/e2e/support/helpers/plugin-settings.ts +++ b/packages/app/e2e/support/helpers/plugin-settings.ts @@ -3,6 +3,8 @@ import { expect, test as base, type Page } from "../fixtures"; import { gotoAppShell, openSettings } from "./app"; import { goBackInSettings, openCompactSettings, openHostSection } from "./settings"; import { getServerId } from "./server-id"; +import { expectAppRoute } from "./route-assertions"; +import { buildSettingsHostSectionRoute } from "@/utils/host-routes"; import { connectNewWorkspaceDaemonClient } from "./new-workspace"; type SettingsClient = Awaited>; @@ -105,6 +107,13 @@ export async function returnAndReopenSettings(page: Page) { await page.getByRole("menuitem", { name: "Display", exact: true }).click(); await expect(page.getByRole("switch", { name: "Show metadata" })).toBeVisible(); } +export async function returnToPluginsPage(page: Page) { + await page.getByRole("button", { name: "Back to plugins", exact: true }).click(); + await expectAppRoute(page, buildSettingsHostSectionRoute(getServerId(), "plugins")); + await expect( + page.getByRole("button", { name: "Actions for settings-example", exact: true }), + ).toBeVisible(); +} export async function expectSettingsUnavailable(page: Page) { await expect( page.getByText("This plugin settings screen is unavailable.", { exact: true }), diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index ef1e65f2ef..663058713a 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -2071,6 +2071,7 @@ export const en = { offline: "Connect to this host to open plugin settings.", update: "Update this host to use plugin settings.", unavailable: "This plugin settings screen is unavailable.", + backToPlugins: "Back to plugins", }, trustedTitle: "Plugins are trusted code", trustedDescription: diff --git a/packages/app/src/i18n/resources/plugin-settings.ts b/packages/app/src/i18n/resources/plugin-settings.ts index 15373a822d..104bf6b1d0 100644 --- a/packages/app/src/i18n/resources/plugin-settings.ts +++ b/packages/app/src/i18n/resources/plugin-settings.ts @@ -18,6 +18,7 @@ export const pluginSettings = { offline: "اتصل بهذا المضيف لفتح إعدادات الإضافة.", update: "حدّث هذا المضيف لاستخدام إعدادات الإضافة.", unavailable: "شاشة إعدادات الإضافة هذه غير متاحة.", + backToPlugins: "العودة إلى الإضافات", }, title: "الإضافات", trustedTitle: "الإضافات تعليمات برمجية موثوقة", @@ -72,6 +73,7 @@ export const pluginSettings = { offline: "Conéctate a este host para abrir los ajustes del plugin.", update: "Actualiza este host para usar los ajustes del plugin.", unavailable: "Esta pantalla de ajustes del plugin no está disponible.", + backToPlugins: "Volver a plugins", }, title: "Plugins", trustedTitle: "Los plugins son código de confianza", @@ -126,6 +128,7 @@ export const pluginSettings = { offline: "Connectez-vous à cet hôte pour ouvrir les paramètres du plugin.", update: "Mettez à jour cet hôte pour utiliser les paramètres du plugin.", unavailable: "Cet écran de paramètres du plugin est indisponible.", + backToPlugins: "Retour aux plugins", }, title: "Plugins", trustedTitle: "Les plugins sont du code de confiance", @@ -180,6 +183,7 @@ export const pluginSettings = { offline: "このホストに接続してプラグイン設定を開いてください。", update: "プラグイン設定を使用するには、このホストを更新してください。", unavailable: "このプラグイン設定画面は利用できません。", + backToPlugins: "プラグインに戻る", }, title: "プラグイン", trustedTitle: "プラグインは信頼済みコードです", @@ -234,6 +238,7 @@ export const pluginSettings = { offline: "플러그인 설정을 열려면 이 호스트에 연결하세요.", update: "플러그인 설정을 사용하려면 이 호스트를 업데이트하세요.", unavailable: "이 플러그인 설정 화면을 사용할 수 없습니다.", + backToPlugins: "플러그인으로 돌아가기", }, title: "플러그인", trustedTitle: "플러그인은 신뢰하는 코드입니다", @@ -287,6 +292,7 @@ export const pluginSettings = { offline: "Conecte-se a este host para abrir as configurações do plugin.", update: "Atualize este host para usar as configurações do plugin.", unavailable: "Esta tela de configurações do plugin está indisponível.", + backToPlugins: "Voltar para plugins", }, title: "Plugins", trustedTitle: "Plugins são código confiável", @@ -341,6 +347,7 @@ export const pluginSettings = { offline: "Подключитесь к этому хосту, чтобы открыть настройки плагина.", update: "Обновите этот хост, чтобы использовать настройки плагина.", unavailable: "Этот экран настроек плагина недоступен.", + backToPlugins: "Вернуться к плагинам", }, title: "Плагины", trustedTitle: "Плагины — доверенный код", @@ -395,6 +402,7 @@ export const pluginSettings = { offline: "连接到此主机以打开插件设置。", update: "更新此主机以使用插件设置。", unavailable: "此插件设置页面不可用。", + backToPlugins: "返回插件", }, title: "插件", trustedTitle: "插件是受信任的代码", diff --git a/packages/app/src/plugins/settings/index.tsx b/packages/app/src/plugins/settings/index.tsx index 191ecc20f2..11b5c199df 100644 --- a/packages/app/src/plugins/settings/index.tsx +++ b/packages/app/src/plugins/settings/index.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { Platform, Text, View } from "react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { router } from "expo-router"; +import { ArrowLeft } from "lucide-react-native"; import type { PluginHostProps } from "@getpaseo/plugin/client"; import { Button } from "@/components/ui/button"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; @@ -127,7 +128,30 @@ function SettingsContent({ } const ThemedSettingsContent = withUnistyles(SettingsContent); const themeMapping = (theme: Theme) => ({ theme: toPluginTheme(theme) }); -export function PluginSettingsContent(props: SettingsIdentity) { - return ; +export function PluginSettingsContent({ + onBackToPlugins, + showBackToPlugins, + ...identity +}: SettingsIdentity & { onBackToPlugins: () => void; showBackToPlugins: boolean }) { + const { t } = useTranslation(); + return ( + + {showBackToPlugins ? ( + + ) : null} + + + ); } -const styles = StyleSheet.create((theme) => ({ message: { color: theme.colors.foregroundMuted } })); +const styles = StyleSheet.create((theme) => ({ + message: { color: theme.colors.foregroundMuted }, + backButton: { alignSelf: "flex-start", paddingHorizontal: 0, marginBottom: theme.spacing[4] }, +})); diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index dc1a7c46d6..925607a858 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -1333,6 +1333,8 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti serverId={view.serverId} pluginId={view.pluginId} screenId={view.screenId} + onBackToPlugins={handleBackFromDetail} + showBackToPlugins={!isCompactLayout} /> ); if (view.kind === "host") {