feat: Add protocol status handling and repair functionality in About component

This commit is contained in:
2026-05-25 00:34:13 +10:00
parent 8de528da9a
commit a4a0fa6758
2 changed files with 166 additions and 3 deletions

View File

@@ -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 { Box, Text, IconButton, Icon, Icons, Scroll, Button, config, toRem } from 'folds';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card'; 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 PaarrotSVG from '../../../../../public/res/svg/paarrot.svg';
import { clearCacheAndReload } from '../../../../client/initMatrix'; import { clearCacheAndReload } from '../../../../client/initMatrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getVersion } from '@tauri-apps/api/app';
type AboutProps = { type AboutProps = {
requestClose: () => void; requestClose: () => void;
@@ -15,10 +15,100 @@ type AboutProps = {
export function About({ requestClose }: AboutProps) { export function About({ requestClose }: AboutProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [version, setVersion] = useState<string>(''); const [version, setVersion] = useState<string>('');
const [protocolStatus, setProtocolStatus] = useState<string>('Checking desktop protocol integration...');
const [protocolBusy, setProtocolBusy] = useState<boolean>(false);
const formatProtocolStatus = useCallback((data: {
platform: string;
primaryScheme: string;
schemes: Record<string, {
isDefault: boolean;
registerCall: boolean;
error: string | null;
}>;
windows: null | Record<string, {
userChoiceProgId: string | null;
userChoiceCommand: string | null;
hkcuCommand: string | null;
hklmCommand: string | null;
}>;
}): 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(() => { useEffect(() => {
getVersion().then(setVersion).catch(() => setVersion('unknown')); getVersion().then(setVersion).catch(() => setVersion('unknown'));
}, []); refreshProtocolStatus();
}, [refreshProtocolStatus]);
return ( return (
<Page> <Page>
@@ -98,6 +188,36 @@ export function About({ requestClose }: AboutProps) {
</Button> </Button>
} }
/> />
<SettingTile
title="Protocol Handlers (paarrot:// + element://)"
description={protocolStatus}
after={
<Box gap="100">
<Button
onClick={repairProtocolHandler}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
disabled={protocolBusy}
>
<Text size="B300">Repair</Text>
</Button>
<Button
onClick={refreshProtocolStatus}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
disabled={protocolBusy}
>
<Text size="B300">Refresh</Text>
</Button>
</Box>
}
/>
</SequenceCard> </SequenceCard>
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">

43
src/ext.d.ts vendored
View File

@@ -57,6 +57,49 @@ interface ElectronAPI {
writeText: (text: string) => void; writeText: (text: string) => void;
}; };
plugins?: PluginAPI; plugins?: PluginAPI;
protocol?: {
getStatus: () => Promise<{
success: boolean;
data?: {
platform: string;
primaryScheme: string;
schemes: Record<string, {
isDefault: boolean;
before: boolean;
registerCall: boolean;
error: string | null;
}>;
windows: null | Record<string, {
userChoiceProgId: string | null;
userChoiceCommand: string | null;
hkcuCommand: string | null;
hklmCommand: string | null;
}>;
};
error?: string;
}>;
repair: () => Promise<{
success: boolean;
data?: {
openedSettings: boolean;
platform: string;
primaryScheme: string;
schemes: Record<string, {
isDefault: boolean;
before: boolean;
registerCall: boolean;
error: string | null;
}>;
windows: null | Record<string, {
userChoiceProgId: string | null;
userChoiceCommand: string | null;
hkcuCommand: string | null;
hklmCommand: string | null;
}>;
};
error?: string;
}>;
};
} }
declare global { declare global {