feat: Add protocol status handling and repair functionality in About component
This commit is contained in:
@@ -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
43
src/ext.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user