From a4a0fa675881f9fd6f678084fcb6a1e2decaf1a2 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Mon, 25 May 2026 00:34:13 +1000 Subject: [PATCH] feat: Add protocol status handling and repair functionality in About component --- src/app/features/settings/about/About.tsx | 126 +++++++++++++++++++++- src/ext.d.ts | 43 ++++++++ 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx index f18bcf2..88e4a96 100644 --- a/src/app/features/settings/about/About.tsx +++ b/src/app/features/settings/about/About.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { getVersion } from '@tauri-apps/api/app'; import { Box, Text, IconButton, Icon, Icons, Scroll, Button, config, toRem } from 'folds'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; @@ -7,7 +8,6 @@ import { SettingTile } from '../../../components/setting-tile'; import PaarrotSVG from '../../../../../public/res/svg/paarrot.svg'; import { clearCacheAndReload } from '../../../../client/initMatrix'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { getVersion } from '@tauri-apps/api/app'; type AboutProps = { requestClose: () => void; @@ -15,10 +15,100 @@ type AboutProps = { export function About({ requestClose }: AboutProps) { const mx = useMatrixClient(); const [version, setVersion] = useState(''); + const [protocolStatus, setProtocolStatus] = useState('Checking desktop protocol integration...'); + const [protocolBusy, setProtocolBusy] = useState(false); + + const formatProtocolStatus = useCallback((data: { + platform: string; + primaryScheme: string; + schemes: Record; + windows: null | Record; + }): string => { + const { element } = data.schemes; + const primary = data.schemes[data.primaryScheme]; + const primaryState = primary?.isDefault ? 'registered' : 'not registered'; + const elementState = element?.isDefault ? 'registered' : 'not registered'; + const primaryCall = primary?.registerCall ? 'ok' : 'no-change'; + const elementCall = element?.registerCall ? 'ok' : 'no-change'; + + const primaryError = primary?.error ? ` ${data.primaryScheme} error: ${primary.error}` : ''; + const elementError = element?.error ? ` element error: ${element.error}` : ''; + + const primaryWindows = data.windows?.[data.primaryScheme]; + const primaryOwner = + data.platform === 'win32' && primaryWindows?.userChoiceProgId + ? ` Current ${data.primaryScheme} default ProgId=${primaryWindows.userChoiceProgId}.` + : ''; + const primaryMissingHint = + data.platform === 'win32' && !primaryWindows?.hkcuCommand && !primaryWindows?.hklmCommand + ? ` ${data.primaryScheme} link type is missing from Windows registry. Use Repair to create it.` + : ''; + + return `Primary ${data.primaryScheme} is ${primaryState} (startup=${primaryCall}) on ${data.platform}. Legacy element is ${elementState} (startup=${elementCall}).${primaryOwner}${primaryMissingHint}${primaryError}${elementError}`; + }, []); + + const refreshProtocolStatus = useCallback(() => { + if (!window.electron?.protocol?.getStatus) { + setProtocolStatus('Desktop protocol diagnostics are unavailable in this environment.'); + return; + } + + window.electron.protocol + .getStatus() + .then((result) => { + if (!result?.success || !result.data) { + setProtocolStatus(result?.error ?? 'Unable to query protocol state.'); + return; + } + + setProtocolStatus(formatProtocolStatus(result.data)); + }) + .catch((error) => { + setProtocolStatus(`Protocol diagnostics failed: ${error instanceof Error ? error.message : String(error)}`); + }); + }, [formatProtocolStatus]); + + const repairProtocolHandler = () => { + if (!window.electron?.protocol?.repair) { + setProtocolStatus('Protocol repair is unavailable in this environment.'); + return; + } + + setProtocolBusy(true); + window.electron.protocol + .repair() + .then((result) => { + if (!result?.success || !result.data) { + setProtocolStatus(result?.error ?? 'Protocol repair failed.'); + return; + } + + const settingsHint = + result.data.platform === 'win32' && result.data.openedSettings + ? ' Windows Default Apps was opened; set PAARROT to Paarrot and press Refresh.' + : ''; + setProtocolStatus(`${formatProtocolStatus(result.data)}${settingsHint}`); + setProtocolBusy(false); + }) + .catch((error) => { + setProtocolStatus(`Protocol repair failed: ${error instanceof Error ? error.message : String(error)}`); + setProtocolBusy(false); + }); + }; useEffect(() => { getVersion().then(setVersion).catch(() => setVersion('unknown')); - }, []); + refreshProtocolStatus(); + }, [refreshProtocolStatus]); return ( @@ -98,6 +188,36 @@ export function About({ requestClose }: AboutProps) { } /> + + + + + } + /> diff --git a/src/ext.d.ts b/src/ext.d.ts index 983f499..be71ab2 100644 --- a/src/ext.d.ts +++ b/src/ext.d.ts @@ -57,6 +57,49 @@ interface ElectronAPI { writeText: (text: string) => void; }; plugins?: PluginAPI; + protocol?: { + getStatus: () => Promise<{ + success: boolean; + data?: { + platform: string; + primaryScheme: string; + schemes: Record; + windows: null | Record; + }; + error?: string; + }>; + repair: () => Promise<{ + success: boolean; + data?: { + openedSettings: boolean; + platform: string; + primaryScheme: string; + schemes: Record; + windows: null | Record; + }; + error?: string; + }>; + }; } declare global {