diff --git a/packages/app/e2e/sidebar-help.spec.ts b/packages/app/e2e/sidebar-help.spec.ts new file mode 100644 index 000000000..9f95ed1ca --- /dev/null +++ b/packages/app/e2e/sidebar-help.spec.ts @@ -0,0 +1,100 @@ +import { expect, test, type Page } from "./fixtures"; +import { gotoAppShell, openSettings } from "./helpers/app"; +import { openSettingsSection } from "./helpers/settings"; + +const DISCORD_DESTINATION = + /^https:\/\/(?:discord\.gg\/jz8T2uahpH|discord\.com\/invite\/jz8T2uahpH)(?:[/?#]|$)/; +const GITHUB_ISSUE_DESTINATION = + /^https:\/\/github\.com\/(?:getpaseo\/paseo\/issues\/new(?:\/choose)?(?:[/?#]|$)|login\?return_to=https%3A%2F%2Fgithub\.com%2Fgetpaseo%2Fpaseo%2Fissues%2Fnew$)/; +const APP_VERSION = /^Paseo v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; + +async function openHelpMenu(page: Page): Promise { + await page.getByTestId("sidebar-help").click(); + await expect(page.getByTestId("sidebar-help-menu")).toBeVisible(); +} + +async function expectDiagnosticReport(page: Page): Promise { + const sheet = page.getByTestId("app-diagnostic-sheet"); + await expect(sheet).toBeVisible(); + await expect(sheet.getByRole("button", { name: "Copy diagnostic" })).toBeEnabled(); + await expect(page.getByText(/App version:/).first()).toBeVisible(); +} + +async function closeSheet(page: Page, testID: string): Promise { + const sheet = page.getByTestId(testID); + await sheet.getByLabel("Close").click(); + await expect(sheet).not.toBeVisible(); +} + +async function expectExternalPage( + page: Page, + actionTestID: string, + expectedUrl: RegExp, +): Promise { + const popupPromise = page.waitForEvent("popup"); + await page.getByTestId(actionTestID).click(); + const popup = await popupPromise; + expect(popup.url()).toMatch(expectedUrl); + await popup.close(); +} + +test("opens troubleshooting tools from the sidebar help menu", async ({ page }) => { + await gotoAppShell(page); + await expect(page.getByTestId("sidebar-help")).toBeVisible(); + + await openHelpMenu(page); + const triggerBox = await page.getByTestId("sidebar-help").evaluate((element) => { + const { y, height } = element.getBoundingClientRect(); + return { y, height }; + }); + const menuBox = await page.getByTestId("sidebar-help-menu").evaluate((element) => { + const { y, height } = element.getBoundingClientRect(); + return { y, height }; + }); + expect(menuBox.y + menuBox.height).toBeLessThanOrEqual(triggerBox.y); + await expect(page.getByText("Troubleshoot", { exact: true })).toBeVisible(); + await expect(page.getByText("Report an issue", { exact: true })).toBeVisible(); + await expect(page.getByTestId("sidebar-help-version")).toHaveText(APP_VERSION); + + await page.getByTestId("sidebar-help-diagnostics").click(); + await expectDiagnosticReport(page); + await closeSheet(page, "app-diagnostic-sheet"); + + await openHelpMenu(page); + await page.getByTestId("sidebar-help-shortcuts").click(); + await expect(page.getByTestId("keyboard-shortcuts-dialog")).toBeVisible(); + await closeSheet(page, "keyboard-shortcuts-dialog"); +}); + +test("opens the preferred issue-reporting destinations", async ({ page }) => { + await gotoAppShell(page); + + await openHelpMenu(page); + await expectExternalPage(page, "sidebar-help-discord", DISCORD_DESTINATION); + + await openHelpMenu(page); + await expectExternalPage(page, "sidebar-help-github", GITHUB_ISSUE_DESTINATION); +}); + +test("keeps diagnostics available from Settings after globalizing the sheet", async ({ page }) => { + await gotoAppShell(page); + await openSettings(page); + await openSettingsSection(page, "diagnostics"); + + await page.getByRole("button", { name: "Run", exact: true }).click(); + await expectDiagnosticReport(page); +}); + +test.describe("compact sidebar help", () => { + test.use({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true }); + + test("offers diagnostics without advertising disabled keyboard shortcuts", async ({ page }) => { + await gotoAppShell(page); + await page.getByRole("button", { name: "Open menu", exact: true }).click(); + + await openHelpMenu(page); + await expect(page.getByTestId("sidebar-help-shortcuts")).toHaveCount(0); + await page.getByTestId("sidebar-help-diagnostics").click(); + await expectDiagnosticReport(page); + }); +}); diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index db6db10e5..29319ab95 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -26,6 +26,7 @@ import { WorktreeSetupCalloutSource } from "@/components/worktree-setup-callout- import { DownloadToast } from "@/components/download-toast"; import { QuittingOverlay } from "@/components/quitting-overlay"; import { KeyboardShortcutsDialog } from "@/components/keyboard-shortcuts-dialog"; +import { AppDiagnosticHost } from "@/components/app-diagnostic-host"; import { LeftSidebar } from "@/components/left-sidebar"; import { SidebarModelProvider } from "@/components/sidebar/sidebar-model"; import { CompactExplorerSidebarHost } from "@/components/compact-explorer-sidebar-host"; @@ -487,6 +488,7 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon + ); diff --git a/packages/app/src/components/app-diagnostic-host.tsx b/packages/app/src/components/app-diagnostic-host.tsx new file mode 100644 index 000000000..03c423935 --- /dev/null +++ b/packages/app/src/components/app-diagnostic-host.tsx @@ -0,0 +1,18 @@ +import { AppDiagnosticSheet } from "@/components/app-diagnostic-sheet"; +import { isElectronRuntime } from "@/desktop/host"; +import { useAppDiagnosticStore } from "@/diagnostics/store"; +import { resolveAppVersion } from "@/utils/app-version"; + +export function AppDiagnosticHost() { + const visible = useAppDiagnosticStore((state) => state.visible); + const close = useAppDiagnosticStore((state) => state.close); + + return ( + + ); +} diff --git a/packages/app/src/components/left-sidebar.tsx b/packages/app/src/components/left-sidebar.tsx index 7d62105a2..db0145b47 100644 --- a/packages/app/src/components/left-sidebar.tsx +++ b/packages/app/src/components/left-sidebar.tsx @@ -28,6 +28,7 @@ import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region"; import { HostPicker } from "@/components/hosts/host-picker"; import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row"; import { SidebarDisplayPreferencesMenu } from "@/components/sidebar/sidebar-display-preferences-menu"; +import { SidebarHelpMenu } from "@/components/sidebar/sidebar-help-menu"; import { Shortcut } from "@/components/ui/shortcut"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useIsCompactFormFactor } from "@/constants/layout"; @@ -532,6 +533,7 @@ function SidebarFooter({ shortcutKeys={settingsKeys} theme={theme} /> + ); diff --git a/packages/app/src/components/sidebar/sidebar-help-menu.tsx b/packages/app/src/components/sidebar/sidebar-help-menu.tsx new file mode 100644 index 000000000..315bc8f15 --- /dev/null +++ b/packages/app/src/components/sidebar/sidebar-help-menu.tsx @@ -0,0 +1,156 @@ +import { useCallback, useState } from "react"; +import { Text, View } from "react-native"; +import { Activity, CircleHelp, Keyboard } from "lucide-react-native"; +import { useTranslation } from "react-i18next"; +import { StyleSheet, withUnistyles } from "react-native-unistyles"; +import { DiscordIcon } from "@/components/icons/discord-icon"; +import { GitHubIcon } from "@/components/icons/github-icon"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuHint, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useIsCompactFormFactor } from "@/constants/layout"; +import { isNative } from "@/constants/platform"; +import { useAppDiagnosticStore } from "@/diagnostics/store"; +import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store"; +import { ICON_SIZE, type Theme } from "@/styles/theme"; +import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates"; +import { resolveAppVersion } from "@/utils/app-version"; +import { openExternalUrl } from "@/utils/open-external-url"; + +const DISCORD_URL = "https://discord.gg/jz8T2uahpH"; +const GITHUB_ISSUE_URL = "https://github.com/getpaseo/paseo/issues/new"; +const ThemedActivity = withUnistyles(Activity); +const ThemedCircleHelp = withUnistyles(CircleHelp); +const ThemedKeyboard = withUnistyles(Keyboard); +const ThemedDiscordIcon = withUnistyles(DiscordIcon); +const ThemedGitHubIcon = withUnistyles(GitHubIcon); +const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); +const foregroundMutedColorMapping = (theme: Theme) => ({ + color: theme.colors.foregroundMuted, +}); +const diagnosticLeadingIcon = ( + +); +const shortcutsLeadingIcon = ( + +); +const discordLeadingIcon = ( + +); +const githubLeadingIcon = ( + +); + +export function SidebarHelpMenu() { + const { t } = useTranslation(); + const isCompactLayout = useIsCompactFormFactor(); + const openAppDiagnostic = useAppDiagnosticStore((state) => state.open); + const setShortcutsDialogOpen = useKeyboardShortcutsStore((state) => state.setShortcutsDialogOpen); + const [open, setOpen] = useState(false); + const showKeyboardShortcuts = !isNative && !isCompactLayout; + const version = formatVersionWithPrefix(resolveAppVersion()); + + const openKeyboardShortcuts = useCallback(() => { + setShortcutsDialogOpen(true); + }, [setShortcutsDialogOpen]); + + const openDiscord = useCallback(() => { + void openExternalUrl(DISCORD_URL); + }, []); + + const openGitHubIssue = useCallback(() => { + void openExternalUrl(GITHUB_ISSUE_URL); + }, []); + + return ( + + + + + + {({ hovered }) => ( + + )} + + + + + {t("sidebar.help.trigger")} + + + + {t("sidebar.help.troubleshoot")} + + {t("sidebar.help.diagnostics")} + + {showKeyboardShortcuts ? ( + + {t("sidebar.help.shortcuts")} + + ) : null} + + {t("sidebar.help.reportIssue")} + + {t("sidebar.help.discord")} + + + {t("sidebar.help.github")} + + + + {t("sidebar.help.version", { version })} + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + trigger: { + width: 28, + height: 28, + alignItems: "center", + justifyContent: "center", + paddingVertical: theme.spacing[1], + paddingHorizontal: theme.spacing[1], + }, + tooltipText: { + fontSize: theme.fontSize.sm, + color: theme.colors.popoverForeground, + }, +})); diff --git a/packages/app/src/diagnostics/store.ts b/packages/app/src/diagnostics/store.ts new file mode 100644 index 000000000..3852d8953 --- /dev/null +++ b/packages/app/src/diagnostics/store.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +interface AppDiagnosticState { + visible: boolean; + open: () => void; + close: () => void; +} + +export const useAppDiagnosticStore = create((set) => ({ + visible: false, + open: () => set({ visible: true }), + close: () => set({ visible: false }), +})); diff --git a/packages/app/src/hooks/open-project.ts b/packages/app/src/hooks/open-project.ts index bff4432ba..d3b4e5406 100644 --- a/packages/app/src/hooks/open-project.ts +++ b/packages/app/src/hooks/open-project.ts @@ -6,6 +6,8 @@ import { type EmptyProjectDescriptor as ProjectWithoutWorkspacesDescriptor, type WorkspaceDescriptor, } from "@/stores/session-store"; +import { generateDraftId } from "@/stores/draft-keys"; +import type { NavigateToWorkspaceInput } from "@/stores/navigation-active-workspace-store"; import { buildWorkspaceTabPersistenceKey } from "@/stores/workspace-tabs-store"; type OpenProjectPayload = ProjectAddResponse["payload"]; @@ -57,8 +59,7 @@ interface WorkspaceOpenCallbacks { isConnected: boolean; mergeWorkspaces: (serverId: string, workspaces: Iterable) => void; setHasHydratedWorkspaces: (serverId: string, hydrated: boolean) => void; - openDraftTab: (workspaceKey: string) => string | null; - navigateToWorkspace: (serverId: string, workspaceId: string) => void; + navigateToWorkspace: (input: NavigateToWorkspaceInput) => string; } export interface OpenGithubRepoDirectlyInput extends WorkspaceOpenCallbacks { @@ -122,8 +123,11 @@ function finishWorkspaceOpen( input.mergeWorkspaces(normalizedServerId, [workspace]); input.setHasHydratedWorkspaces(normalizedServerId, true); - input.openDraftTab(workspaceKey); - input.navigateToWorkspace(normalizedServerId, workspace.id); + input.navigateToWorkspace({ + serverId: normalizedServerId, + workspaceId: workspace.id, + target: { kind: "draft", draftId: generateDraftId() }, + }); return true; } diff --git a/packages/app/src/hooks/use-open-project.test.ts b/packages/app/src/hooks/use-open-project.test.ts index 16d86c131..4acd65d25 100644 --- a/packages/app/src/hooks/use-open-project.test.ts +++ b/packages/app/src/hooks/use-open-project.test.ts @@ -8,6 +8,7 @@ import type { EmptyProjectDescriptor as ProjectWithoutWorkspacesDescriptor, WorkspaceDescriptor, } from "@/stores/session-store"; +import type { NavigateToWorkspaceInput } from "@/stores/navigation-active-workspace-store"; const SERVER_ID = "server-1"; const PROJECT_PATH = "/repo/project"; @@ -55,15 +56,6 @@ interface RecordedHydrated { hydrated: boolean; } -interface RecordedOpenDraftTab { - workspaceKey: string; -} - -interface RecordedNavigate { - serverId: string; - workspaceId: string; -} - interface RecordedClone { repo: string; targetDirectory: string; @@ -90,23 +82,13 @@ function createFakeSession() { }; } -function createFakeWorkspaceLayout() { - const openedTabs: RecordedOpenDraftTab[] = []; - return { - openedTabs, - openDraftTab: (workspaceKey: string) => { - openedTabs.push({ workspaceKey }); - return "tab-1"; - }, - }; -} - function createFakeNavigator() { - const navigations: RecordedNavigate[] = []; + const navigations: NavigateToWorkspaceInput[] = []; return { navigations, - navigateToWorkspace: (serverId: string, workspaceId: string) => { - navigations.push({ serverId, workspaceId }); + navigateToWorkspace: (input: NavigateToWorkspaceInput) => { + navigations.push(input); + return `/hosts/${input.serverId}/workspaces/${input.workspaceId}`; }, }; } @@ -226,7 +208,6 @@ describe("openProjectDirectly", () => { describe("openGithubRepoDirectly", () => { it("opens a cloned GitHub workspace and seeds a draft tab", async () => { const session = createFakeSession(); - const layout = createFakeWorkspaceLayout(); const navigator = createFakeNavigator(); const workspacePayload = buildWorkspacePayload(); const github = createFakeGithubCloneClient(workspacePayload); @@ -240,7 +221,6 @@ describe("openGithubRepoDirectly", () => { client: github, mergeWorkspaces: session.mergeWorkspaces, setHasHydratedWorkspaces: session.setHasHydratedWorkspaces, - openDraftTab: layout.openDraftTab, navigateToWorkspace: navigator.navigateToWorkspace, }); @@ -261,13 +241,17 @@ describe("openGithubRepoDirectly", () => { workspaceDirectory: PROJECT_PATH, }); expect(session.hydrated).toEqual([{ serverId: SERVER_ID, hydrated: true }]); - expect(layout.openedTabs).toEqual([{ workspaceKey: `${SERVER_ID}:1` }]); - expect(navigator.navigations).toEqual([{ serverId: SERVER_ID, workspaceId: "1" }]); + expect(navigator.navigations).toEqual([ + { + serverId: SERVER_ID, + workspaceId: "1", + target: { kind: "draft", draftId: expect.any(String) }, + }, + ]); }); it("rejects a workspace without an identity before changing app state", async () => { const session = createFakeSession(); - const layout = createFakeWorkspaceLayout(); const navigator = createFakeNavigator(); const github = createFakeGithubCloneClient({ ...buildWorkspacePayload(), id: " " }); @@ -280,14 +264,12 @@ describe("openGithubRepoDirectly", () => { client: github, mergeWorkspaces: session.mergeWorkspaces, setHasHydratedWorkspaces: session.setHasHydratedWorkspaces, - openDraftTab: layout.openDraftTab, navigateToWorkspace: navigator.navigateToWorkspace, }); expect(result).toBe(false); expect(session.merges).toEqual([]); expect(session.hydrated).toEqual([]); - expect(layout.openedTabs).toEqual([]); expect(navigator.navigations).toEqual([]); }); }); diff --git a/packages/app/src/hooks/use-open-project.ts b/packages/app/src/hooks/use-open-project.ts index 97347d38c..181610b20 100644 --- a/packages/app/src/hooks/use-open-project.ts +++ b/packages/app/src/hooks/use-open-project.ts @@ -1,8 +1,6 @@ import { useCallback } from "react"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; import { useSessionStore } from "@/stores/session-store"; -import { useWorkspaceLayoutStore } from "@/stores/workspace-layout-store"; -import { generateDraftId } from "@/stores/draft-keys"; import { navigateToWorkspace } from "@/stores/navigation-active-workspace-store"; import { openGithubRepoDirectly, @@ -61,12 +59,6 @@ export function useOpenGithubRepo( const isConnected = useHostRuntimeIsConnected(normalizedServerId); const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces); const setHasHydratedWorkspaces = useSessionStore((state) => state.setHasHydratedWorkspaces); - const openDraftTab = useCallback((workspaceKey: string) => { - return useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, { - kind: "draft", - draftId: generateDraftId(), - }); - }, []); return useCallback( async (repo: string, targetDirectory: string, cloneProtocol?: WorkspaceGithubCloneProtocol) => { @@ -79,17 +71,9 @@ export function useOpenGithubRepo( client, mergeWorkspaces, setHasHydratedWorkspaces, - openDraftTab, navigateToWorkspace, }); }, - [ - client, - isConnected, - mergeWorkspaces, - normalizedServerId, - openDraftTab, - setHasHydratedWorkspaces, - ], + [client, isConnected, mergeWorkspaces, normalizedServerId, setHasHydratedWorkspaces], ); } diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index ad09ff2ba..27554a016 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -790,6 +790,20 @@ export const ar: TranslationResources = { settings: "إعدادات", closeSidebar: "إغلاق الشريط الجانبي", }, + help: { + trigger: "المساعدة والدعم", + troubleshoot: "استكشاف الأخطاء وإصلاحها", + diagnostics: "تشغيل التشخيص", + diagnosticsDescription: "جمع تفاصيل التطبيق والمضيفين المتصلين", + shortcuts: "اختصارات لوحة المفاتيح", + shortcutsDescription: "عرض اختصارات لوحة المفاتيح المتاحة", + reportIssue: "الإبلاغ عن مشكلة", + discord: "Discord", + discordDescription: "الأفضل للمساعدة السريعة والنقاش", + github: "إنشاء مشكلة على GitHub", + githubDescription: "الإبلاغ عن خطأ يمكن إعادة إنتاجه", + version: "Paseo {{version}}", + }, sections: { sessions: "السجل", schedules: "الجداول", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 7f95dfba0..73ada70f3 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -797,6 +797,20 @@ export const en = { settings: "Settings", closeSidebar: "Close sidebar", }, + help: { + trigger: "Help and support", + troubleshoot: "Troubleshoot", + diagnostics: "Run diagnostics", + diagnosticsDescription: "Collect app and connected host details", + shortcuts: "Keyboard shortcuts", + shortcutsDescription: "View available keyboard shortcuts", + reportIssue: "Report an issue", + discord: "Discord", + discordDescription: "Best for quick help and discussion", + github: "Create GitHub issue", + githubDescription: "Report a reproducible bug", + version: "Paseo {{version}}", + }, sections: { sessions: "History", schedules: "Schedules", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 5cc22d340..c57fda7e1 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -817,6 +817,20 @@ export const es: TranslationResources = { settings: "Ajustes", closeSidebar: "Cerrar barra lateral", }, + help: { + trigger: "Ayuda y soporte", + troubleshoot: "Solucionar problemas", + diagnostics: "Ejecutar diagnóstico", + diagnosticsDescription: "Recopila datos de la app y los hosts conectados", + shortcuts: "Atajos de teclado", + shortcutsDescription: "Ver los atajos de teclado disponibles", + reportIssue: "Informar de un problema", + discord: "Discord", + discordDescription: "La mejor opción para ayuda rápida y conversación", + github: "Crear incidencia en GitHub", + githubDescription: "Informar de un error reproducible", + version: "Paseo {{version}}", + }, sections: { sessions: "Historial", schedules: "Horarios", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index 496601cb4..829a15c1c 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -816,6 +816,20 @@ export const fr: TranslationResources = { settings: "Paramètres", closeSidebar: "Fermer la barre latérale", }, + help: { + trigger: "Aide et assistance", + troubleshoot: "Dépannage", + diagnostics: "Lancer le diagnostic", + diagnosticsDescription: "Collecter les détails de l’app et des hôtes connectés", + shortcuts: "Raccourcis clavier", + shortcutsDescription: "Afficher les raccourcis clavier disponibles", + reportIssue: "Signaler un problème", + discord: "Discord", + discordDescription: "Idéal pour obtenir une aide rapide et échanger", + github: "Créer un ticket GitHub", + githubDescription: "Signaler un bug reproductible", + version: "Paseo {{version}}", + }, sections: { sessions: "Historique", schedules: "Planifications", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index c54d6e8d6..9514b45ea 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -802,6 +802,20 @@ export const ja: TranslationResources = { settings: "設定", closeSidebar: "サイドバーを閉じる", }, + help: { + trigger: "ヘルプとサポート", + troubleshoot: "トラブルシューティング", + diagnostics: "診断を実行", + diagnosticsDescription: "アプリと接続中のホストの詳細を収集", + shortcuts: "キーボードショートカット", + shortcutsDescription: "利用可能なキーボードショートカットを表示", + reportIssue: "問題を報告", + discord: "Discord", + discordDescription: "すばやいサポートや相談に最適", + github: "GitHub Issueを作成", + githubDescription: "再現可能なバグを報告", + version: "Paseo {{version}}", + }, sections: { sessions: "履歴", schedules: "スケジュール", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index ccd2973d3..582c9d261 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -808,6 +808,20 @@ export const ptBR: TranslationResources = { settings: "Configurações", closeSidebar: "Fechar barra lateral", }, + help: { + trigger: "Ajuda e suporte", + troubleshoot: "Resolver problemas", + diagnostics: "Executar diagnóstico", + diagnosticsDescription: "Coletar detalhes do app e dos hosts conectados", + shortcuts: "Atalhos de teclado", + shortcutsDescription: "Ver os atalhos de teclado disponíveis", + reportIssue: "Relatar um problema", + discord: "Discord", + discordDescription: "Ideal para ajuda rápida e conversa", + github: "Criar issue no GitHub", + githubDescription: "Relatar um bug reproduzível", + version: "Paseo {{version}}", + }, sections: { sessions: "Histórico", schedules: "Agendamentos", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 5be0232e3..59cea3eb2 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -809,6 +809,20 @@ export const ru: TranslationResources = { settings: "Настройки", closeSidebar: "Закрыть боковую панель", }, + help: { + trigger: "Помощь и поддержка", + troubleshoot: "Устранение неполадок", + diagnostics: "Запустить диагностику", + diagnosticsDescription: "Собрать данные приложения и подключённых хостов", + shortcuts: "Сочетания клавиш", + shortcutsDescription: "Показать доступные сочетания клавиш", + reportIssue: "Сообщить о проблеме", + discord: "Discord", + discordDescription: "Для быстрой помощи и обсуждения", + github: "Создать issue в GitHub", + githubDescription: "Сообщить о воспроизводимой ошибке", + version: "Paseo {{version}}", + }, sections: { sessions: "История", schedules: "Расписания", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index bb34514c9..c1bc82c41 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -785,6 +785,20 @@ export const zhCN: TranslationResources = { settings: "设置", closeSidebar: "关闭侧边栏", }, + help: { + trigger: "帮助与支持", + troubleshoot: "问题排查", + diagnostics: "运行诊断", + diagnosticsDescription: "收集应用和已连接 Host 的详细信息", + shortcuts: "键盘快捷键", + shortcutsDescription: "查看可用的键盘快捷键", + reportIssue: "报告问题", + discord: "Discord", + discordDescription: "适合快速求助和讨论", + github: "创建 GitHub Issue", + githubDescription: "报告可复现的 bug", + version: "Paseo {{version}}", + }, sections: { sessions: "历史", schedules: "计划", diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index e004ca00e..33347130c 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -35,7 +35,6 @@ import { SquareTerminal, } from "lucide-react-native"; import { DropdownTrigger } from "@/components/ui/dropdown-trigger"; -import { AppDiagnosticSheet } from "@/components/app-diagnostic-sheet"; import { ComboboxTrigger } from "@/components/ui/combobox-trigger"; import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row"; import { SidebarSeparator } from "@/components/sidebar/sidebar-separator"; @@ -76,6 +75,7 @@ import { isElectronRuntime } from "@/desktop/host"; import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater"; import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates"; import { resolveAppVersion } from "@/utils/app-version"; +import { useAppDiagnosticStore } from "@/diagnostics/store"; import { settingsStyles } from "@/styles/settings"; import { THINKING_TONE_NATIVE_PCM_BASE64 } from "@/utils/thinking-tone.native-pcm"; import { useVoiceAudioEngineOptional } from "@/contexts/voice-context"; @@ -445,8 +445,6 @@ interface DiagnosticsSectionProps { isPlaybackTestRunning: boolean; playbackTestResult: string | null; handlePlaybackTest: () => Promise; - appVersion: string | null; - isDesktopApp: boolean; } function DiagnosticsSection({ @@ -454,16 +452,12 @@ function DiagnosticsSection({ isPlaybackTestRunning, playbackTestResult, handlePlaybackTest, - appVersion, - isDesktopApp, }: DiagnosticsSectionProps) { const { t } = useTranslation(); - const [diagnosticSheetOpen, setDiagnosticSheetOpen] = useState(false); + const openAppDiagnostic = useAppDiagnosticStore((state) => state.open); const handlePlayPress = useCallback(() => { void handlePlaybackTest(); }, [handlePlaybackTest]); - const handleOpenDiagnostic = useCallback(() => setDiagnosticSheetOpen(true), []); - const handleCloseDiagnostic = useCallback(() => setDiagnosticSheetOpen(false), []); return ( @@ -472,7 +466,7 @@ function DiagnosticsSection({ {t("settings.diagnostics.app.rowTitle")} {t("settings.diagnostics.app.rowHint")} - @@ -495,12 +489,6 @@ function DiagnosticsSection({ - ); } @@ -1429,8 +1417,6 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti isPlaybackTestRunning={isPlaybackTestRunning} playbackTestResult={playbackTestResult} handlePlaybackTest={handlePlaybackTest} - appVersion={appVersion} - isDesktopApp={isDesktopApp} /> ); case "about":