fix(desktop): restore notification sounds (#2582)
Restore desktop notification sounds by default, add a persisted Play sound setting, and group notification permission and testing in a dedicated Notifications section.
This commit is contained in:
@@ -31,10 +31,11 @@ export function Alert({
|
|||||||
}: AlertProps) {
|
}: AlertProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const accentColor = resolveAccentColor(variant, theme);
|
const accentColor = resolveAccentColor(variant, theme);
|
||||||
|
const borderColor = variant === "success" ? theme.colors.border : accentColor;
|
||||||
|
|
||||||
const containerStyle = useMemo(
|
const containerStyle = useMemo(
|
||||||
() => [styles.container, accentColor ? { borderColor: accentColor } : null],
|
() => [styles.container, borderColor ? { borderColor } : null],
|
||||||
[accentColor],
|
[borderColor],
|
||||||
);
|
);
|
||||||
|
|
||||||
const titleStyle = useMemo(
|
const titleStyle = useMemo(
|
||||||
@@ -73,7 +74,7 @@ function resolveAccentColor(
|
|||||||
theme: ReturnType<typeof useUnistyles>["theme"],
|
theme: ReturnType<typeof useUnistyles>["theme"],
|
||||||
): string | null {
|
): string | null {
|
||||||
if (variant === "info") return theme.colors.palette.blue[300];
|
if (variant === "info") return theme.colors.palette.blue[300];
|
||||||
if (variant === "success") return theme.colors.palette.green[400];
|
if (variant === "success") return theme.colors.statusSuccess;
|
||||||
if (variant === "warning") return theme.colors.palette.amber[500];
|
if (variant === "warning") return theme.colors.palette.amber[500];
|
||||||
if (variant === "error") return theme.colors.destructive;
|
if (variant === "error") return theme.colors.destructive;
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Text, View } from "react-native";
|
||||||
|
import { withUnistyles } from "react-native-unistyles";
|
||||||
|
import { RotateCw } from "lucide-react-native";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
||||||
|
import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permissions";
|
||||||
|
import { useDesktopSettings } from "@/desktop/settings/desktop-settings";
|
||||||
|
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||||
|
import { settingsStyles } from "@/styles/settings";
|
||||||
|
|
||||||
|
const ThemedRotateCw = withUnistyles(RotateCw, (theme) => ({
|
||||||
|
size: theme.iconSize.md,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function DesktopNotificationsSection() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { settings, isSaving, updateSettings } = useDesktopSettings();
|
||||||
|
const {
|
||||||
|
isDesktopApp,
|
||||||
|
snapshot,
|
||||||
|
isRefreshing,
|
||||||
|
requestingPermission,
|
||||||
|
testNotificationState,
|
||||||
|
refreshPermissions,
|
||||||
|
requestPermission,
|
||||||
|
sendTestNotification,
|
||||||
|
} = useDesktopPermissions();
|
||||||
|
|
||||||
|
const handleRefreshPress = useCallback(() => {
|
||||||
|
void refreshPermissions();
|
||||||
|
}, [refreshPermissions]);
|
||||||
|
|
||||||
|
const handleRequestNotifications = useCallback(() => {
|
||||||
|
void requestPermission("notifications");
|
||||||
|
}, [requestPermission]);
|
||||||
|
|
||||||
|
const handlePlaySoundChange = useCallback(
|
||||||
|
(playSound: boolean) => {
|
||||||
|
void updateSettings({ notifications: { playSound } }).catch(() => {
|
||||||
|
// useDesktopSettings owns the user-visible IPC error.
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSendTestNotification = useCallback(() => {
|
||||||
|
void sendTestNotification();
|
||||||
|
}, [sendTestNotification]);
|
||||||
|
|
||||||
|
const isPermissionBusy = isRefreshing || requestingPermission !== null;
|
||||||
|
const isSendingTestNotification = testNotificationState.status === "sending";
|
||||||
|
const refreshIcon = useMemo(() => <ThemedRotateCw />, []);
|
||||||
|
const refreshButton = useMemo(
|
||||||
|
() => (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={refreshIcon}
|
||||||
|
onPress={handleRefreshPress}
|
||||||
|
disabled={isPermissionBusy}
|
||||||
|
accessibilityLabel={t("settings.notifications.refreshAccessibility")}
|
||||||
|
>
|
||||||
|
{isRefreshing ? t("settings.permissions.refreshing") : t("settings.permissions.refresh")}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
[handleRefreshPress, isPermissionBusy, isRefreshing, refreshIcon, t],
|
||||||
|
);
|
||||||
|
const permissionLabels = useMemo(
|
||||||
|
() => ({
|
||||||
|
granted: t("settings.permissions.actions.granted"),
|
||||||
|
request: t("settings.permissions.actions.request"),
|
||||||
|
requesting: t("settings.permissions.actions.requesting"),
|
||||||
|
}),
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDesktopApp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationsGranted = snapshot?.notifications.state === "granted";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsSection title={t("settings.notifications.title")} trailing={refreshButton}>
|
||||||
|
<View style={settingsStyles.card}>
|
||||||
|
<DesktopPermissionRow
|
||||||
|
title={t("settings.notifications.permission")}
|
||||||
|
status={snapshot?.notifications ?? null}
|
||||||
|
isRequesting={requestingPermission === "notifications"}
|
||||||
|
onRequest={handleRequestNotifications}
|
||||||
|
labels={permissionLabels}
|
||||||
|
/>
|
||||||
|
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||||
|
<View style={settingsStyles.rowContent}>
|
||||||
|
<Text style={settingsStyles.rowTitle}>{t("settings.notifications.playSound")}</Text>
|
||||||
|
<Text style={settingsStyles.rowHint}>{t("settings.notifications.playSoundHint")}</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={settings.notifications.playSound}
|
||||||
|
onValueChange={handlePlaySoundChange}
|
||||||
|
disabled={isSaving}
|
||||||
|
accessibilityLabel={t("settings.notifications.playSound")}
|
||||||
|
testID="desktop-notifications-play-sound-switch"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||||
|
<View style={settingsStyles.rowContent}>
|
||||||
|
<Text style={settingsStyles.rowTitle}>{t("settings.notifications.test")}</Text>
|
||||||
|
<Text style={settingsStyles.rowHint}>
|
||||||
|
{notificationsGranted
|
||||||
|
? t("settings.notifications.testHint")
|
||||||
|
: t("settings.notifications.permissionRequired")}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onPress={handleSendTestNotification}
|
||||||
|
disabled={!notificationsGranted || isPermissionBusy || isSendingTestNotification}
|
||||||
|
>
|
||||||
|
{isSendingTestNotification
|
||||||
|
? t("settings.notifications.sending")
|
||||||
|
: t("settings.notifications.send")}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{testNotificationState.status === "success" ? (
|
||||||
|
<Alert
|
||||||
|
variant="success"
|
||||||
|
title={t("settings.notifications.sentTitle")}
|
||||||
|
description={t("settings.notifications.sentDescription")}
|
||||||
|
testID="desktop-notifications-test-success"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{testNotificationState.status === "error" ? (
|
||||||
|
<Alert
|
||||||
|
variant="error"
|
||||||
|
title={t("settings.notifications.sendFailedTitle")}
|
||||||
|
description={testNotificationState.message}
|
||||||
|
testID="desktop-notifications-test-error"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</SettingsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { View, Text } from "react-native";
|
import { View, Text } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||||
import { Check } from "lucide-react-native";
|
import { Check } from "lucide-react-native";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { settingsStyles } from "@/styles/settings";
|
import { settingsStyles } from "@/styles/settings";
|
||||||
@@ -14,16 +14,16 @@ export interface DesktopPermissionRowProps {
|
|||||||
granted: string;
|
granted: string;
|
||||||
request: string;
|
request: string;
|
||||||
requesting: string;
|
requesting: string;
|
||||||
busyExtraAction: (label: string) => string;
|
|
||||||
};
|
};
|
||||||
showBorder?: boolean;
|
showBorder?: boolean;
|
||||||
onRequest: () => void;
|
onRequest: () => void;
|
||||||
extraActionLabel?: string;
|
|
||||||
isExtraActionBusy?: boolean;
|
|
||||||
isExtraActionDisabled?: boolean;
|
|
||||||
onExtraAction?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ThemedCheck = withUnistyles(Check, (theme) => ({
|
||||||
|
size: theme.iconSize.sm,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
}));
|
||||||
|
|
||||||
export function DesktopPermissionRow({
|
export function DesktopPermissionRow({
|
||||||
title,
|
title,
|
||||||
status,
|
status,
|
||||||
@@ -31,12 +31,7 @@ export function DesktopPermissionRow({
|
|||||||
labels,
|
labels,
|
||||||
showBorder,
|
showBorder,
|
||||||
onRequest,
|
onRequest,
|
||||||
extraActionLabel,
|
|
||||||
isExtraActionBusy = false,
|
|
||||||
isExtraActionDisabled = false,
|
|
||||||
onExtraAction,
|
|
||||||
}: DesktopPermissionRowProps) {
|
}: DesktopPermissionRowProps) {
|
||||||
const { theme } = useUnistyles();
|
|
||||||
const state = status?.state ?? "unknown";
|
const state = status?.state ?? "unknown";
|
||||||
const isGranted = state === "granted";
|
const isGranted = state === "granted";
|
||||||
const shouldShowDetail =
|
const shouldShowDetail =
|
||||||
@@ -58,21 +53,9 @@ export function DesktopPermissionRow({
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.permissionRowActions}>
|
<View style={styles.permissionRowActions}>
|
||||||
{isGranted ? (
|
{isGranted ? (
|
||||||
<View style={styles.permissionGrantedActions}>
|
<View style={styles.permissionStatusPill}>
|
||||||
<View style={styles.permissionStatusPill}>
|
<ThemedCheck />
|
||||||
<Check size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
<Text style={styles.permissionStatusText}>{labels.granted}</Text>
|
||||||
<Text style={styles.permissionStatusText}>{labels.granted}</Text>
|
|
||||||
</View>
|
|
||||||
{extraActionLabel && onExtraAction ? (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onPress={onExtraAction}
|
|
||||||
disabled={isExtraActionDisabled || isExtraActionBusy}
|
|
||||||
>
|
|
||||||
{isExtraActionBusy ? labels.busyExtraAction(extraActionLabel) : extraActionLabel}
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="outline" size="sm" onPress={onRequest} disabled={isRequesting}>
|
<Button variant="outline" size="sm" onPress={onRequest} disabled={isRequesting}>
|
||||||
@@ -92,11 +75,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
permissionGrantedActions: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
permissionStatusPill: {
|
permissionStatusPill: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View, Text } from "react-native";
|
import { View } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { withUnistyles } from "react-native-unistyles";
|
||||||
import { RotateCw } from "lucide-react-native";
|
import { RotateCw } from "lucide-react-native";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
||||||
@@ -9,49 +9,33 @@ import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permiss
|
|||||||
import { settingsStyles } from "@/styles/settings";
|
import { settingsStyles } from "@/styles/settings";
|
||||||
import { SettingsSection } from "@/screens/settings/settings-section";
|
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||||
|
|
||||||
|
const ThemedRotateCw = withUnistyles(RotateCw, (theme) => ({
|
||||||
|
size: theme.iconSize.md,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
}));
|
||||||
|
|
||||||
export function DesktopPermissionsSection() {
|
export function DesktopPermissionsSection() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { theme } = useUnistyles();
|
|
||||||
const {
|
const {
|
||||||
isDesktopApp,
|
isDesktopApp,
|
||||||
snapshot,
|
snapshot,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
requestingPermission,
|
requestingPermission,
|
||||||
isSendingTestNotification,
|
|
||||||
testNotificationError,
|
|
||||||
refreshPermissions,
|
refreshPermissions,
|
||||||
requestPermission,
|
requestPermission,
|
||||||
sendTestNotification,
|
|
||||||
} = useDesktopPermissions();
|
} = useDesktopPermissions();
|
||||||
|
|
||||||
const errorTextStyle = useMemo(
|
|
||||||
() => [styles.errorText, { color: theme.colors.destructive }],
|
|
||||||
[theme.colors.destructive],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRefreshPress = useCallback(() => {
|
const handleRefreshPress = useCallback(() => {
|
||||||
void refreshPermissions();
|
void refreshPermissions();
|
||||||
}, [refreshPermissions]);
|
}, [refreshPermissions]);
|
||||||
|
|
||||||
const handleRequestNotifications = useCallback(() => {
|
|
||||||
void requestPermission("notifications");
|
|
||||||
}, [requestPermission]);
|
|
||||||
|
|
||||||
const handleRequestMicrophone = useCallback(() => {
|
const handleRequestMicrophone = useCallback(() => {
|
||||||
void requestPermission("microphone");
|
void requestPermission("microphone");
|
||||||
}, [requestPermission]);
|
}, [requestPermission]);
|
||||||
|
|
||||||
const handleSendTestNotification = useCallback(() => {
|
|
||||||
void sendTestNotification();
|
|
||||||
}, [sendTestNotification]);
|
|
||||||
|
|
||||||
const isBusy = isRefreshing || requestingPermission !== null;
|
const isBusy = isRefreshing || requestingPermission !== null;
|
||||||
const notificationsGranted = snapshot?.notifications.state === "granted";
|
|
||||||
|
|
||||||
const refreshIcon = useMemo(
|
const refreshIcon = useMemo(() => <ThemedRotateCw />, []);
|
||||||
() => <RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />,
|
|
||||||
[theme.iconSize.md, theme.colors.foregroundMuted],
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshButton = useMemo(
|
const refreshButton = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@@ -74,7 +58,6 @@ export function DesktopPermissionsSection() {
|
|||||||
granted: t("settings.permissions.actions.granted"),
|
granted: t("settings.permissions.actions.granted"),
|
||||||
request: t("settings.permissions.actions.request"),
|
request: t("settings.permissions.actions.request"),
|
||||||
requesting: t("settings.permissions.actions.requesting"),
|
requesting: t("settings.permissions.actions.requesting"),
|
||||||
busyExtraAction: (label: string) => t("settings.permissions.actions.busySuffix", { label }),
|
|
||||||
}),
|
}),
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
@@ -86,21 +69,8 @@ export function DesktopPermissionsSection() {
|
|||||||
return (
|
return (
|
||||||
<SettingsSection title={t("settings.permissions.title")} trailing={refreshButton}>
|
<SettingsSection title={t("settings.permissions.title")} trailing={refreshButton}>
|
||||||
<View style={settingsStyles.card}>
|
<View style={settingsStyles.card}>
|
||||||
<DesktopPermissionRow
|
|
||||||
title={t("settings.permissions.notifications")}
|
|
||||||
status={snapshot?.notifications ?? null}
|
|
||||||
isRequesting={requestingPermission === "notifications"}
|
|
||||||
onRequest={handleRequestNotifications}
|
|
||||||
labels={permissionLabels}
|
|
||||||
extraActionLabel={t("settings.permissions.test")}
|
|
||||||
isExtraActionBusy={isSendingTestNotification}
|
|
||||||
isExtraActionDisabled={!notificationsGranted || isBusy}
|
|
||||||
onExtraAction={handleSendTestNotification}
|
|
||||||
/>
|
|
||||||
{testNotificationError ? <Text style={errorTextStyle}>{testNotificationError}</Text> : null}
|
|
||||||
<DesktopPermissionRow
|
<DesktopPermissionRow
|
||||||
title={t("settings.permissions.microphone")}
|
title={t("settings.permissions.microphone")}
|
||||||
showBorder
|
|
||||||
status={snapshot?.microphone ?? null}
|
status={snapshot?.microphone ?? null}
|
||||||
isRequesting={requestingPermission === "microphone"}
|
isRequesting={requestingPermission === "microphone"}
|
||||||
onRequest={handleRequestMicrophone}
|
onRequest={handleRequestMicrophone}
|
||||||
@@ -110,11 +80,3 @@ export function DesktopPermissionsSection() {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
|
||||||
errorText: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingBottom: theme.spacing[2],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|||||||
@@ -14,13 +14,18 @@ export interface UseDesktopPermissionsReturn {
|
|||||||
snapshot: DesktopPermissionSnapshot | null;
|
snapshot: DesktopPermissionSnapshot | null;
|
||||||
isRefreshing: boolean;
|
isRefreshing: boolean;
|
||||||
requestingPermission: DesktopPermissionKind | null;
|
requestingPermission: DesktopPermissionKind | null;
|
||||||
isSendingTestNotification: boolean;
|
testNotificationState: TestNotificationState;
|
||||||
testNotificationError: string | null;
|
|
||||||
refreshPermissions: () => Promise<void>;
|
refreshPermissions: () => Promise<void>;
|
||||||
requestPermission: (kind: DesktopPermissionKind) => Promise<void>;
|
requestPermission: (kind: DesktopPermissionKind) => Promise<void>;
|
||||||
sendTestNotification: () => Promise<void>;
|
sendTestNotification: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TestNotificationState =
|
||||||
|
| { status: "idle" }
|
||||||
|
| { status: "sending" }
|
||||||
|
| { status: "success" }
|
||||||
|
| { status: "error"; message: string };
|
||||||
|
|
||||||
export function useDesktopPermissions(): UseDesktopPermissionsReturn {
|
export function useDesktopPermissions(): UseDesktopPermissionsReturn {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isDesktopApp = shouldShowDesktopPermissionSection();
|
const isDesktopApp = shouldShowDesktopPermissionSection();
|
||||||
@@ -30,7 +35,9 @@ export function useDesktopPermissions(): UseDesktopPermissionsReturn {
|
|||||||
const [requestingPermission, setRequestingPermission] = useState<DesktopPermissionKind | null>(
|
const [requestingPermission, setRequestingPermission] = useState<DesktopPermissionKind | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [isSendingTestNotification, setIsSendingTestNotification] = useState(false);
|
const [testNotificationState, setTestNotificationState] = useState<TestNotificationState>({
|
||||||
|
status: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -111,28 +118,34 @@ export function useDesktopPermissions(): UseDesktopPermissionsReturn {
|
|||||||
[isDesktopApp, refreshPermissions, t],
|
[isDesktopApp, refreshPermissions, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [testNotificationError, setTestNotificationError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const sendTestNotification = useCallback(async () => {
|
const sendTestNotification = useCallback(async () => {
|
||||||
if (!isDesktopApp) {
|
if (!isDesktopApp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSendingTestNotification(true);
|
setTestNotificationState({ status: "sending" });
|
||||||
setTestNotificationError(null);
|
|
||||||
try {
|
try {
|
||||||
const sent = await sendOsNotification({
|
const sent = await sendOsNotification({
|
||||||
title: t("desktop.permissions.testNotification.title"),
|
title: t("desktop.permissions.testNotification.title"),
|
||||||
body: t("desktop.permissions.testNotification.body"),
|
body: t("desktop.permissions.testNotification.body"),
|
||||||
});
|
});
|
||||||
if (!sent) {
|
if (!isMountedRef.current) {
|
||||||
setTestNotificationError(t("desktop.permissions.testNotification.notDelivered"));
|
return;
|
||||||
}
|
}
|
||||||
|
setTestNotificationState(
|
||||||
|
sent
|
||||||
|
? { status: "success" }
|
||||||
|
: {
|
||||||
|
status: "error",
|
||||||
|
message: t("desktop.permissions.testNotification.notDelivered"),
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
setTestNotificationError(t("desktop.permissions.testNotification.failed"));
|
|
||||||
} finally {
|
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
setIsSendingTestNotification(false);
|
setTestNotificationState({
|
||||||
|
status: "error",
|
||||||
|
message: t("desktop.permissions.testNotification.failed"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isDesktopApp, t]);
|
}, [isDesktopApp, t]);
|
||||||
@@ -150,8 +163,7 @@ export function useDesktopPermissions(): UseDesktopPermissionsReturn {
|
|||||||
snapshot,
|
snapshot,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
requestingPermission,
|
requestingPermission,
|
||||||
isSendingTestNotification,
|
testNotificationState,
|
||||||
testNotificationError,
|
|
||||||
refreshPermissions,
|
refreshPermissions,
|
||||||
requestPermission,
|
requestPermission,
|
||||||
sendTestNotification,
|
sendTestNotification,
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ const DESKTOP_SETTINGS_QUERY_KEY = ["desktop-settings"] as const;
|
|||||||
|
|
||||||
export interface DesktopSettings {
|
export interface DesktopSettings {
|
||||||
releaseChannel: ReleaseChannel;
|
releaseChannel: ReleaseChannel;
|
||||||
|
notifications: {
|
||||||
|
playSound: boolean;
|
||||||
|
};
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: boolean;
|
manageBuiltInDaemon: boolean;
|
||||||
keepRunningAfterQuit: boolean;
|
keepRunningAfterQuit: boolean;
|
||||||
@@ -21,11 +24,15 @@ export interface DesktopSettings {
|
|||||||
|
|
||||||
export interface DesktopSettingsPatch {
|
export interface DesktopSettingsPatch {
|
||||||
releaseChannel?: ReleaseChannel;
|
releaseChannel?: ReleaseChannel;
|
||||||
|
notifications?: Partial<DesktopSettings["notifications"]>;
|
||||||
daemon?: Partial<DesktopSettings["daemon"]>;
|
daemon?: Partial<DesktopSettings["daemon"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||||
releaseChannel: "stable",
|
releaseChannel: "stable",
|
||||||
|
notifications: {
|
||||||
|
playSound: true,
|
||||||
|
},
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
@@ -144,10 +151,17 @@ export async function migrateLegacyDesktopSettings(input: {
|
|||||||
|
|
||||||
function parseDesktopSettings(raw: unknown): DesktopSettings {
|
function parseDesktopSettings(raw: unknown): DesktopSettings {
|
||||||
const record = isRecord(raw) ? raw : {};
|
const record = isRecord(raw) ? raw : {};
|
||||||
|
const notifications = isRecord(record.notifications) ? record.notifications : {};
|
||||||
const daemon = isRecord(record.daemon) ? record.daemon : {};
|
const daemon = isRecord(record.daemon) ? record.daemon : {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
releaseChannel: record.releaseChannel === "beta" ? "beta" : "stable",
|
releaseChannel: record.releaseChannel === "beta" ? "beta" : "stable",
|
||||||
|
notifications: {
|
||||||
|
playSound:
|
||||||
|
typeof notifications.playSound === "boolean"
|
||||||
|
? notifications.playSound
|
||||||
|
: DEFAULT_DESKTOP_SETTINGS.notifications.playSound,
|
||||||
|
},
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon:
|
manageBuiltInDaemon:
|
||||||
typeof daemon.manageBuiltInDaemon === "boolean"
|
typeof daemon.manageBuiltInDaemon === "boolean"
|
||||||
@@ -167,6 +181,10 @@ function mergeDesktopSettings(
|
|||||||
): DesktopSettings {
|
): DesktopSettings {
|
||||||
return {
|
return {
|
||||||
releaseChannel: updates.releaseChannel ?? current.releaseChannel,
|
releaseChannel: updates.releaseChannel ?? current.releaseChannel,
|
||||||
|
notifications: {
|
||||||
|
...current.notifications,
|
||||||
|
...updates.notifications,
|
||||||
|
},
|
||||||
daemon: {
|
daemon: {
|
||||||
...current.daemon,
|
...current.daemon,
|
||||||
...updates.daemon,
|
...updates.daemon,
|
||||||
@@ -177,6 +195,7 @@ function mergeDesktopSettings(
|
|||||||
function normalizePatch(updates: DesktopSettingsPatch): Record<string, unknown> {
|
function normalizePatch(updates: DesktopSettingsPatch): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
...(updates.releaseChannel ? { releaseChannel: updates.releaseChannel } : {}),
|
...(updates.releaseChannel ? { releaseChannel: updates.releaseChannel } : {}),
|
||||||
|
...(updates.notifications ? { notifications: updates.notifications } : {}),
|
||||||
...(updates.daemon ? { daemon: updates.daemon } : {}),
|
...(updates.daemon ? { daemon: updates.daemon } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export interface FakeDesktopBridge extends DesktopSettingsBridge {
|
|||||||
|
|
||||||
const DEFAULT_DESKTOP: DesktopSettings = {
|
const DEFAULT_DESKTOP: DesktopSettings = {
|
||||||
releaseChannel: "stable",
|
releaseChannel: "stable",
|
||||||
|
notifications: {
|
||||||
|
playSound: true,
|
||||||
|
},
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ describe("loadSettingsFromStorage", () => {
|
|||||||
isElectron: true,
|
isElectron: true,
|
||||||
settings: {
|
settings: {
|
||||||
releaseChannel: "beta",
|
releaseChannel: "beta",
|
||||||
|
notifications: { playSound: true },
|
||||||
daemon: { manageBuiltInDaemon: false, keepRunningAfterQuit: true },
|
daemon: { manageBuiltInDaemon: false, keepRunningAfterQuit: true },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -246,7 +246,9 @@ describe("translation resources", () => {
|
|||||||
expect(en.settings.shortcuts.actions.rebind).toBe("Rebind");
|
expect(en.settings.shortcuts.actions.rebind).toBe("Rebind");
|
||||||
expect(en.settings.integrations.commandLine.title).toBe("Command line");
|
expect(en.settings.integrations.commandLine.title).toBe("Command line");
|
||||||
expect(en.settings.integrations.skills.updateAvailable).toBe("Update available");
|
expect(en.settings.integrations.skills.updateAvailable).toBe("Update available");
|
||||||
expect(en.settings.permissions.notifications).toBe("Notifications");
|
expect(en.settings.notifications.playSound).toBe("Play sound");
|
||||||
|
expect(en.settings.notifications.permission).toBe("Notification permission");
|
||||||
|
expect(en.settings.notifications.sentTitle).toBe("Test notification sent");
|
||||||
expect(en.settings.permissions.actions.request).toBe("Request");
|
expect(en.settings.permissions.actions.request).toBe("Request");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1703,6 +1703,7 @@ export const ar: TranslationResources = {
|
|||||||
editor: "المحرر",
|
editor: "المحرر",
|
||||||
shortcuts: "الاختصارات",
|
shortcuts: "الاختصارات",
|
||||||
integrations: "التكامل",
|
integrations: "التكامل",
|
||||||
|
notifications: "الإشعارات",
|
||||||
permissions: "الأذونات",
|
permissions: "الأذونات",
|
||||||
diagnostics: "التشخيص",
|
diagnostics: "التشخيص",
|
||||||
about: "عن",
|
about: "عن",
|
||||||
@@ -1712,6 +1713,21 @@ export const ar: TranslationResources = {
|
|||||||
vimKeybindings: "اختصارات Vim",
|
vimKeybindings: "اختصارات Vim",
|
||||||
vimHint: "تنطبق على ملفات المصدر في الويب وسطح المكتب.",
|
vimHint: "تنطبق على ملفات المصدر في الويب وسطح المكتب.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "الإشعارات",
|
||||||
|
permission: "إذن الإشعارات",
|
||||||
|
refreshAccessibility: "تحديث إذن الإشعارات",
|
||||||
|
playSound: "تشغيل صوت",
|
||||||
|
playSoundHint: "تشغيل صوت عند وصول إشعار سطح مكتب",
|
||||||
|
test: "اختبار الإشعار",
|
||||||
|
testHint: "إرسال إشعار باستخدام هذه الإعدادات",
|
||||||
|
permissionRequired: "اسمح بالوصول إلى الإشعارات قبل الاختبار",
|
||||||
|
send: "إرسال",
|
||||||
|
sending: "جارٍ الإرسال...",
|
||||||
|
sentTitle: "تم إرسال إشعار الاختبار",
|
||||||
|
sentDescription: "سلّم Paseo الإشعار إلى نظام التشغيل.",
|
||||||
|
sendFailedTitle: "تعذر إرسال إشعار الاختبار",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "المشاريع",
|
projects: "المشاريع",
|
||||||
connections: "اتصالات",
|
connections: "اتصالات",
|
||||||
@@ -2006,17 +2022,14 @@ export const ar: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "الأذونات",
|
title: "الأذونات",
|
||||||
notifications: "إشعارات",
|
|
||||||
microphone: "ميكروفون",
|
microphone: "ميكروفون",
|
||||||
refresh: "ينعش",
|
refresh: "ينعش",
|
||||||
refreshing: "منعش...",
|
refreshing: "منعش...",
|
||||||
refreshAccessibility: "تحديث أذونات سطح المكتب",
|
refreshAccessibility: "تحديث أذونات سطح المكتب",
|
||||||
test: "امتحان",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "ممنوح",
|
granted: "ممنوح",
|
||||||
request: "طلب",
|
request: "طلب",
|
||||||
requesting: "جارٍ الطلب...",
|
requesting: "جارٍ الطلب...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1714,6 +1714,7 @@ export const en = {
|
|||||||
editor: "Editor",
|
editor: "Editor",
|
||||||
shortcuts: "Shortcuts",
|
shortcuts: "Shortcuts",
|
||||||
integrations: "Integrations",
|
integrations: "Integrations",
|
||||||
|
notifications: "Notifications",
|
||||||
permissions: "Permissions",
|
permissions: "Permissions",
|
||||||
diagnostics: "Diagnostics",
|
diagnostics: "Diagnostics",
|
||||||
about: "About",
|
about: "About",
|
||||||
@@ -1723,6 +1724,21 @@ export const en = {
|
|||||||
vimKeybindings: "Vim keybindings",
|
vimKeybindings: "Vim keybindings",
|
||||||
vimHint: "Applies to source files on web and desktop.",
|
vimHint: "Applies to source files on web and desktop.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "Notifications",
|
||||||
|
permission: "Notification permission",
|
||||||
|
refreshAccessibility: "Refresh notification permission",
|
||||||
|
playSound: "Play sound",
|
||||||
|
playSoundHint: "Play a sound when a desktop notification arrives",
|
||||||
|
test: "Test notification",
|
||||||
|
testHint: "Send a notification using these settings",
|
||||||
|
permissionRequired: "Allow notification access before testing",
|
||||||
|
send: "Send",
|
||||||
|
sending: "Sending...",
|
||||||
|
sentTitle: "Test notification sent",
|
||||||
|
sentDescription: "Paseo handed the notification to the operating system.",
|
||||||
|
sendFailedTitle: "Unable to send test notification",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "Projects",
|
projects: "Projects",
|
||||||
connections: "Connections",
|
connections: "Connections",
|
||||||
@@ -2017,17 +2033,14 @@ export const en = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "Permissions",
|
title: "Permissions",
|
||||||
notifications: "Notifications",
|
|
||||||
microphone: "Microphone",
|
microphone: "Microphone",
|
||||||
refresh: "Refresh",
|
refresh: "Refresh",
|
||||||
refreshing: "Refreshing...",
|
refreshing: "Refreshing...",
|
||||||
refreshAccessibility: "Refresh desktop permissions",
|
refreshAccessibility: "Refresh desktop permissions",
|
||||||
test: "Test",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "Granted",
|
granted: "Granted",
|
||||||
request: "Request",
|
request: "Request",
|
||||||
requesting: "Requesting...",
|
requesting: "Requesting...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1746,6 +1746,7 @@ export const es: TranslationResources = {
|
|||||||
editor: "Editor",
|
editor: "Editor",
|
||||||
shortcuts: "Atajos",
|
shortcuts: "Atajos",
|
||||||
integrations: "Integraciones",
|
integrations: "Integraciones",
|
||||||
|
notifications: "Notificaciones",
|
||||||
permissions: "Permisos",
|
permissions: "Permisos",
|
||||||
diagnostics: "Diagnóstico",
|
diagnostics: "Diagnóstico",
|
||||||
about: "Acerca de",
|
about: "Acerca de",
|
||||||
@@ -1755,6 +1756,21 @@ export const es: TranslationResources = {
|
|||||||
vimKeybindings: "Atajos de Vim",
|
vimKeybindings: "Atajos de Vim",
|
||||||
vimHint: "Se aplica a archivos fuente en web y escritorio.",
|
vimHint: "Se aplica a archivos fuente en web y escritorio.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "Notificaciones",
|
||||||
|
permission: "Permiso de notificaciones",
|
||||||
|
refreshAccessibility: "Actualizar permiso de notificaciones",
|
||||||
|
playSound: "Reproducir sonido",
|
||||||
|
playSoundHint: "Reproduce un sonido cuando llega una notificación de escritorio",
|
||||||
|
test: "Probar notificación",
|
||||||
|
testHint: "Envía una notificación con esta configuración",
|
||||||
|
permissionRequired: "Permite el acceso a las notificaciones antes de probar",
|
||||||
|
send: "Enviar",
|
||||||
|
sending: "Enviando...",
|
||||||
|
sentTitle: "Notificación de prueba enviada",
|
||||||
|
sentDescription: "Paseo entregó la notificación al sistema operativo.",
|
||||||
|
sendFailedTitle: "No se pudo enviar la notificación de prueba",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "Proyectos",
|
projects: "Proyectos",
|
||||||
connections: "Conexiones",
|
connections: "Conexiones",
|
||||||
@@ -2056,17 +2072,14 @@ export const es: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "Permisos",
|
title: "Permisos",
|
||||||
notifications: "Notificaciones",
|
|
||||||
microphone: "Micrófono",
|
microphone: "Micrófono",
|
||||||
refresh: "Refrescar",
|
refresh: "Refrescar",
|
||||||
refreshing: "Refrescante...",
|
refreshing: "Refrescante...",
|
||||||
refreshAccessibility: "Actualizar permisos de escritorio",
|
refreshAccessibility: "Actualizar permisos de escritorio",
|
||||||
test: "Prueba",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "Otorgada",
|
granted: "Otorgada",
|
||||||
request: "Pedido",
|
request: "Pedido",
|
||||||
requesting: "Solicitando...",
|
requesting: "Solicitando...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1750,6 +1750,7 @@ export const fr: TranslationResources = {
|
|||||||
editor: "Éditeur",
|
editor: "Éditeur",
|
||||||
shortcuts: "Raccourcis",
|
shortcuts: "Raccourcis",
|
||||||
integrations: "Intégrations",
|
integrations: "Intégrations",
|
||||||
|
notifications: "Notifications",
|
||||||
permissions: "Autorisations",
|
permissions: "Autorisations",
|
||||||
diagnostics: "Diagnostic",
|
diagnostics: "Diagnostic",
|
||||||
about: "À propos",
|
about: "À propos",
|
||||||
@@ -1759,6 +1760,21 @@ export const fr: TranslationResources = {
|
|||||||
vimKeybindings: "Raccourcis Vim",
|
vimKeybindings: "Raccourcis Vim",
|
||||||
vimHint: "S’applique aux fichiers source sur le web et le bureau.",
|
vimHint: "S’applique aux fichiers source sur le web et le bureau.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "Notifications",
|
||||||
|
permission: "Autorisation des notifications",
|
||||||
|
refreshAccessibility: "Actualiser l’autorisation des notifications",
|
||||||
|
playSound: "Émettre un son",
|
||||||
|
playSoundHint: "Émet un son lorsqu’une notification de bureau arrive",
|
||||||
|
test: "Tester la notification",
|
||||||
|
testHint: "Envoie une notification avec ces réglages",
|
||||||
|
permissionRequired: "Autorisez l’accès aux notifications avant le test",
|
||||||
|
send: "Envoyer",
|
||||||
|
sending: "Envoi...",
|
||||||
|
sentTitle: "Notification de test envoyée",
|
||||||
|
sentDescription: "Paseo a transmis la notification au système d’exploitation.",
|
||||||
|
sendFailedTitle: "Impossible d’envoyer la notification de test",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "Projets",
|
projects: "Projets",
|
||||||
connections: "Relations",
|
connections: "Relations",
|
||||||
@@ -2060,17 +2076,14 @@ export const fr: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "Autorisations",
|
title: "Autorisations",
|
||||||
notifications: "Notifications",
|
|
||||||
microphone: "Microphone",
|
microphone: "Microphone",
|
||||||
refresh: "Rafraîchir",
|
refresh: "Rafraîchir",
|
||||||
refreshing: "Rafraîchissant...",
|
refreshing: "Rafraîchissant...",
|
||||||
refreshAccessibility: "Actualiser les autorisations du bureau",
|
refreshAccessibility: "Actualiser les autorisations du bureau",
|
||||||
test: "Test",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "Accordé",
|
granted: "Accordé",
|
||||||
request: "Demande",
|
request: "Demande",
|
||||||
requesting: "Demander...",
|
requesting: "Demander...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1719,6 +1719,7 @@ export const ja: TranslationResources = {
|
|||||||
editor: "エディター",
|
editor: "エディター",
|
||||||
shortcuts: "ショートカット",
|
shortcuts: "ショートカット",
|
||||||
integrations: "連携",
|
integrations: "連携",
|
||||||
|
notifications: "通知",
|
||||||
permissions: "権限",
|
permissions: "権限",
|
||||||
diagnostics: "診断",
|
diagnostics: "診断",
|
||||||
about: "アプリ情報",
|
about: "アプリ情報",
|
||||||
@@ -1728,6 +1729,21 @@ export const ja: TranslationResources = {
|
|||||||
vimKeybindings: "Vim キーバインド",
|
vimKeybindings: "Vim キーバインド",
|
||||||
vimHint: "Web とデスクトップのソースファイルに適用されます。",
|
vimHint: "Web とデスクトップのソースファイルに適用されます。",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "通知",
|
||||||
|
permission: "通知の権限",
|
||||||
|
refreshAccessibility: "通知の権限を更新",
|
||||||
|
playSound: "サウンドを再生",
|
||||||
|
playSoundHint: "デスクトップ通知が届いたときにサウンドを再生します",
|
||||||
|
test: "通知をテスト",
|
||||||
|
testHint: "現在の設定でテスト通知を送信します",
|
||||||
|
permissionRequired: "テストする前に通知へのアクセスを許可してください",
|
||||||
|
send: "送信",
|
||||||
|
sending: "送信中...",
|
||||||
|
sentTitle: "テスト通知を送信しました",
|
||||||
|
sentDescription: "Paseo が通知をオペレーティングシステムに渡しました。",
|
||||||
|
sendFailedTitle: "テスト通知を送信できません",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "プロジェクト",
|
projects: "プロジェクト",
|
||||||
connections: "接続",
|
connections: "接続",
|
||||||
@@ -2025,17 +2041,14 @@ export const ja: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "権限",
|
title: "権限",
|
||||||
notifications: "通知",
|
|
||||||
microphone: "マイク",
|
microphone: "マイク",
|
||||||
refresh: "更新",
|
refresh: "更新",
|
||||||
refreshing: "更新中...",
|
refreshing: "更新中...",
|
||||||
refreshAccessibility: "デスクトップの権限を更新",
|
refreshAccessibility: "デスクトップの権限を更新",
|
||||||
test: "テスト",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "許可済み",
|
granted: "許可済み",
|
||||||
request: "許可を求める",
|
request: "許可を求める",
|
||||||
requesting: "許可を求めています...",
|
requesting: "許可を求めています...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1713,6 +1713,7 @@ export const ko: TranslationResources = {
|
|||||||
editor: "편집기",
|
editor: "편집기",
|
||||||
shortcuts: "단축키",
|
shortcuts: "단축키",
|
||||||
integrations: "통합",
|
integrations: "통합",
|
||||||
|
notifications: "알림",
|
||||||
permissions: "권한",
|
permissions: "권한",
|
||||||
diagnostics: "진단",
|
diagnostics: "진단",
|
||||||
about: "정보",
|
about: "정보",
|
||||||
@@ -1722,6 +1723,21 @@ export const ko: TranslationResources = {
|
|||||||
vimKeybindings: "Vim 키 바인딩",
|
vimKeybindings: "Vim 키 바인딩",
|
||||||
vimHint: "웹 및 데스크톱의 소스 파일에 적용됩니다.",
|
vimHint: "웹 및 데스크톱의 소스 파일에 적용됩니다.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "알림",
|
||||||
|
permission: "알림 권한",
|
||||||
|
refreshAccessibility: "알림 권한 새로 고침",
|
||||||
|
playSound: "소리 재생",
|
||||||
|
playSoundHint: "데스크톱 알림이 도착하면 소리를 재생합니다",
|
||||||
|
test: "알림 테스트",
|
||||||
|
testHint: "현재 설정으로 테스트 알림을 보냅니다",
|
||||||
|
permissionRequired: "테스트하기 전에 알림 접근을 허용하세요",
|
||||||
|
send: "보내기",
|
||||||
|
sending: "보내는 중...",
|
||||||
|
sentTitle: "테스트 알림을 보냈습니다",
|
||||||
|
sentDescription: "Paseo가 알림을 운영 체제에 전달했습니다.",
|
||||||
|
sendFailedTitle: "테스트 알림을 보낼 수 없음",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "프로젝트",
|
projects: "프로젝트",
|
||||||
connections: "연결",
|
connections: "연결",
|
||||||
@@ -2018,17 +2034,14 @@ export const ko: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "권한",
|
title: "권한",
|
||||||
notifications: "알림",
|
|
||||||
microphone: "마이크",
|
microphone: "마이크",
|
||||||
refresh: "새로고침",
|
refresh: "새로고침",
|
||||||
refreshing: "새로고침 중...",
|
refreshing: "새로고침 중...",
|
||||||
refreshAccessibility: "데스크톱 권한 새로고침",
|
refreshAccessibility: "데스크톱 권한 새로고침",
|
||||||
test: "테스트",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "허용됨",
|
granted: "허용됨",
|
||||||
request: "요청",
|
request: "요청",
|
||||||
requesting: "요청 중...",
|
requesting: "요청 중...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1732,6 +1732,7 @@ export const ptBR: TranslationResources = {
|
|||||||
editor: "Editor",
|
editor: "Editor",
|
||||||
shortcuts: "Atalhos",
|
shortcuts: "Atalhos",
|
||||||
integrations: "Integrações",
|
integrations: "Integrações",
|
||||||
|
notifications: "Notificações",
|
||||||
permissions: "Permissões",
|
permissions: "Permissões",
|
||||||
diagnostics: "Diagnósticos",
|
diagnostics: "Diagnósticos",
|
||||||
about: "Sobre",
|
about: "Sobre",
|
||||||
@@ -1741,6 +1742,21 @@ export const ptBR: TranslationResources = {
|
|||||||
vimKeybindings: "Atalhos do Vim",
|
vimKeybindings: "Atalhos do Vim",
|
||||||
vimHint: "Aplica-se a arquivos-fonte na web e no desktop.",
|
vimHint: "Aplica-se a arquivos-fonte na web e no desktop.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "Notificações",
|
||||||
|
permission: "Permissão de notificações",
|
||||||
|
refreshAccessibility: "Atualizar permissão de notificações",
|
||||||
|
playSound: "Reproduzir som",
|
||||||
|
playSoundHint: "Reproduz um som quando chega uma notificação da área de trabalho",
|
||||||
|
test: "Testar notificação",
|
||||||
|
testHint: "Envia uma notificação usando estas configurações",
|
||||||
|
permissionRequired: "Permita o acesso às notificações antes de testar",
|
||||||
|
send: "Enviar",
|
||||||
|
sending: "Enviando...",
|
||||||
|
sentTitle: "Notificação de teste enviada",
|
||||||
|
sentDescription: "O Paseo entregou a notificação ao sistema operacional.",
|
||||||
|
sendFailedTitle: "Não foi possível enviar a notificação de teste",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "Projetos",
|
projects: "Projetos",
|
||||||
connections: "Conexões",
|
connections: "Conexões",
|
||||||
@@ -2040,17 +2056,14 @@ export const ptBR: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "Permissões",
|
title: "Permissões",
|
||||||
notifications: "Notificações",
|
|
||||||
microphone: "Microfone",
|
microphone: "Microfone",
|
||||||
refresh: "Atualizar",
|
refresh: "Atualizar",
|
||||||
refreshing: "Atualizando...",
|
refreshing: "Atualizando...",
|
||||||
refreshAccessibility: "Atualizar permissões do desktop",
|
refreshAccessibility: "Atualizar permissões do desktop",
|
||||||
test: "Testar",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "Concedida",
|
granted: "Concedida",
|
||||||
request: "Solicitar",
|
request: "Solicitar",
|
||||||
requesting: "Solicitando...",
|
requesting: "Solicitando...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1737,6 +1737,7 @@ export const ru: TranslationResources = {
|
|||||||
editor: "Редактор",
|
editor: "Редактор",
|
||||||
shortcuts: "Ярлыки",
|
shortcuts: "Ярлыки",
|
||||||
integrations: "Интеграции",
|
integrations: "Интеграции",
|
||||||
|
notifications: "Уведомления",
|
||||||
permissions: "Разрешения",
|
permissions: "Разрешения",
|
||||||
diagnostics: "Диагностика",
|
diagnostics: "Диагностика",
|
||||||
about: "О",
|
about: "О",
|
||||||
@@ -1746,6 +1747,21 @@ export const ru: TranslationResources = {
|
|||||||
vimKeybindings: "Клавиши Vim",
|
vimKeybindings: "Клавиши Vim",
|
||||||
vimHint: "Применяется к исходным файлам в веб- и настольной версии.",
|
vimHint: "Применяется к исходным файлам в веб- и настольной версии.",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "Уведомления",
|
||||||
|
permission: "Разрешение на уведомления",
|
||||||
|
refreshAccessibility: "Обновить разрешение на уведомления",
|
||||||
|
playSound: "Воспроизводить звук",
|
||||||
|
playSoundHint: "Воспроизводит звук при получении уведомления на компьютере",
|
||||||
|
test: "Проверить уведомление",
|
||||||
|
testHint: "Отправляет уведомление с текущими настройками",
|
||||||
|
permissionRequired: "Разрешите доступ к уведомлениям перед проверкой",
|
||||||
|
send: "Отправить",
|
||||||
|
sending: "Отправка...",
|
||||||
|
sentTitle: "Тестовое уведомление отправлено",
|
||||||
|
sentDescription: "Paseo передал уведомление операционной системе.",
|
||||||
|
sendFailedTitle: "Не удалось отправить тестовое уведомление",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "Проекты",
|
projects: "Проекты",
|
||||||
connections: "Соединения",
|
connections: "Соединения",
|
||||||
@@ -2044,17 +2060,14 @@ export const ru: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "Разрешения",
|
title: "Разрешения",
|
||||||
notifications: "Уведомления",
|
|
||||||
microphone: "Микрофон",
|
microphone: "Микрофон",
|
||||||
refresh: "Обновить",
|
refresh: "Обновить",
|
||||||
refreshing: "Освежающий...",
|
refreshing: "Освежающий...",
|
||||||
refreshAccessibility: "Обновить разрешения рабочего стола",
|
refreshAccessibility: "Обновить разрешения рабочего стола",
|
||||||
test: "Тест",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "Предоставленный",
|
granted: "Предоставленный",
|
||||||
request: "Запрос",
|
request: "Запрос",
|
||||||
requesting: "Запрос...",
|
requesting: "Запрос...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -1683,6 +1683,7 @@ export const zhCN: TranslationResources = {
|
|||||||
editor: "编辑器",
|
editor: "编辑器",
|
||||||
shortcuts: "快捷键",
|
shortcuts: "快捷键",
|
||||||
integrations: "集成",
|
integrations: "集成",
|
||||||
|
notifications: "通知",
|
||||||
permissions: "权限",
|
permissions: "权限",
|
||||||
diagnostics: "诊断",
|
diagnostics: "诊断",
|
||||||
about: "关于",
|
about: "关于",
|
||||||
@@ -1692,6 +1693,21 @@ export const zhCN: TranslationResources = {
|
|||||||
vimKeybindings: "Vim 键位",
|
vimKeybindings: "Vim 键位",
|
||||||
vimHint: "适用于网页和桌面端的源文件。",
|
vimHint: "适用于网页和桌面端的源文件。",
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
title: "通知",
|
||||||
|
permission: "通知权限",
|
||||||
|
refreshAccessibility: "刷新通知权限",
|
||||||
|
playSound: "播放声音",
|
||||||
|
playSoundHint: "收到桌面通知时播放声音",
|
||||||
|
test: "测试通知",
|
||||||
|
testHint: "使用当前设置发送测试通知",
|
||||||
|
permissionRequired: "测试前请允许访问通知",
|
||||||
|
send: "发送",
|
||||||
|
sending: "正在发送...",
|
||||||
|
sentTitle: "测试通知已发送",
|
||||||
|
sentDescription: "Paseo 已将通知交给操作系统。",
|
||||||
|
sendFailedTitle: "无法发送测试通知",
|
||||||
|
},
|
||||||
hostSections: {
|
hostSections: {
|
||||||
projects: "项目",
|
projects: "项目",
|
||||||
connections: "连接",
|
connections: "连接",
|
||||||
@@ -1984,17 +2000,14 @@ export const zhCN: TranslationResources = {
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
title: "权限",
|
title: "权限",
|
||||||
notifications: "通知",
|
|
||||||
microphone: "麦克风",
|
microphone: "麦克风",
|
||||||
refresh: "刷新",
|
refresh: "刷新",
|
||||||
refreshing: "正在刷新...",
|
refreshing: "正在刷新...",
|
||||||
refreshAccessibility: "刷新桌面端权限",
|
refreshAccessibility: "刷新桌面端权限",
|
||||||
test: "测试",
|
|
||||||
actions: {
|
actions: {
|
||||||
granted: "已授权",
|
granted: "已授权",
|
||||||
request: "请求",
|
request: "请求",
|
||||||
requesting: "正在请求...",
|
requesting: "正在请求...",
|
||||||
busySuffix: "{{label}}...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
Keyboard,
|
Keyboard,
|
||||||
Stethoscope,
|
Stethoscope,
|
||||||
Info,
|
Info,
|
||||||
|
Bell,
|
||||||
Shield,
|
Shield,
|
||||||
Puzzle,
|
Puzzle,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -78,6 +79,7 @@ import { CommunityLinks } from "@/components/community-links";
|
|||||||
import { SegmentedControl } from "@/components/ui/segmented-control";
|
import { SegmentedControl } from "@/components/ui/segmented-control";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import { DesktopPermissionsSection } from "@/desktop/components/desktop-permissions-section";
|
import { DesktopPermissionsSection } from "@/desktop/components/desktop-permissions-section";
|
||||||
|
import { DesktopNotificationsSection } from "@/desktop/components/desktop-notifications-section";
|
||||||
import { BrowserDataSection } from "@/desktop/browser/settings/browser-data-section";
|
import { BrowserDataSection } from "@/desktop/browser/settings/browser-data-section";
|
||||||
import { IntegrationsSection } from "@/desktop/components/integrations-section";
|
import { IntegrationsSection } from "@/desktop/components/integrations-section";
|
||||||
import { isElectronRuntime } from "@/desktop/host";
|
import { isElectronRuntime } from "@/desktop/host";
|
||||||
@@ -154,6 +156,12 @@ const SIDEBAR_SECTION_ITEMS: SidebarSectionItem[] = [
|
|||||||
icon: Puzzle,
|
icon: Puzzle,
|
||||||
desktopOnly: true,
|
desktopOnly: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "notifications",
|
||||||
|
labelKey: "settings.sections.notifications",
|
||||||
|
icon: Bell,
|
||||||
|
desktopOnly: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "permissions",
|
id: "permissions",
|
||||||
labelKey: "settings.sections.permissions",
|
labelKey: "settings.sections.permissions",
|
||||||
@@ -1427,6 +1435,8 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti
|
|||||||
return isDesktopApp ? <KeyboardShortcutsSection /> : null;
|
return isDesktopApp ? <KeyboardShortcutsSection /> : null;
|
||||||
case "integrations":
|
case "integrations":
|
||||||
return isDesktopApp ? <IntegrationsSection /> : null;
|
return isDesktopApp ? <IntegrationsSection /> : null;
|
||||||
|
case "notifications":
|
||||||
|
return isDesktopApp ? <DesktopNotificationsSection /> : null;
|
||||||
case "permissions":
|
case "permissions":
|
||||||
return isDesktopApp ? <DesktopPermissionsSection /> : null;
|
return isDesktopApp ? <DesktopPermissionsSection /> : null;
|
||||||
case "diagnostics":
|
case "diagnostics":
|
||||||
|
|||||||
@@ -262,6 +262,10 @@ describe("host settings section slugs", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("settings section slugs", () => {
|
describe("settings section slugs", () => {
|
||||||
|
it("includes desktop notification settings", () => {
|
||||||
|
expect(isSettingsSectionSlug("notifications")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("no longer treats daemon as a valid app-level settings section", () => {
|
it("no longer treats daemon as a valid app-level settings section", () => {
|
||||||
expect(isSettingsSectionSlug("daemon")).toBe(false);
|
expect(isSettingsSectionSlug("daemon")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -495,6 +495,7 @@ export const SETTINGS_SECTION_SLUGS = [
|
|||||||
"editor",
|
"editor",
|
||||||
"shortcuts",
|
"shortcuts",
|
||||||
"integrations",
|
"integrations",
|
||||||
|
"notifications",
|
||||||
"permissions",
|
"permissions",
|
||||||
"diagnostics",
|
"diagnostics",
|
||||||
"about",
|
"about",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { app, BrowserWindow, Notification, ipcMain, nativeImage } from "electron";
|
import { app, BrowserWindow, Notification, ipcMain, nativeImage } from "electron";
|
||||||
|
import { getDesktopSettingsStore } from "../settings/desktop-settings-electron.js";
|
||||||
|
|
||||||
interface NotificationInput {
|
interface NotificationInput {
|
||||||
title?: unknown;
|
title?: unknown;
|
||||||
@@ -95,11 +96,12 @@ export function registerNotificationHandlers(): void {
|
|||||||
const body = toTrimmedString(rawInput?.body) ?? undefined;
|
const body = toTrimmedString(rawInput?.body) ?? undefined;
|
||||||
const data = toRecord(rawInput?.data);
|
const data = toRecord(rawInput?.data);
|
||||||
const icon = getNotificationIcon();
|
const icon = getNotificationIcon();
|
||||||
|
const settings = await getDesktopSettingsStore().get();
|
||||||
const notification = new Notification({
|
const notification = new Notification({
|
||||||
title,
|
title,
|
||||||
...(body ? { body } : {}),
|
...(body ? { body } : {}),
|
||||||
...(icon ? { icon } : {}),
|
...(icon ? { icon } : {}),
|
||||||
silent: true,
|
silent: !settings.notifications.playSound,
|
||||||
});
|
});
|
||||||
|
|
||||||
activeNotifications.add(notification);
|
activeNotifications.add(notification);
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ describe("desktop-settings", () => {
|
|||||||
|
|
||||||
expect(settings).toEqual({
|
expect(settings).toEqual({
|
||||||
releaseChannel: "stable",
|
releaseChannel: "stable",
|
||||||
|
notifications: { playSound: true },
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
@@ -102,6 +103,7 @@ describe("desktop-settings", () => {
|
|||||||
|
|
||||||
expect(next).toEqual({
|
expect(next).toEqual({
|
||||||
releaseChannel: "beta",
|
releaseChannel: "beta",
|
||||||
|
notifications: { playSound: true },
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
@@ -110,6 +112,45 @@ describe("desktop-settings", () => {
|
|||||||
expect(files).toEqual(["desktop-settings.json"]);
|
expect(files).toEqual(["desktop-settings.json"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults notification sounds on for existing settings documents", async () => {
|
||||||
|
const userDataPath = await createTempUserDataDir();
|
||||||
|
directories.add(userDataPath);
|
||||||
|
await writeFile(
|
||||||
|
settingsFilePath(userDataPath),
|
||||||
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
settings: {
|
||||||
|
releaseChannel: "stable",
|
||||||
|
daemon: {
|
||||||
|
manageBuiltInDaemon: true,
|
||||||
|
keepRunningAfterQuit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
legacyRendererSettingsImported: true,
|
||||||
|
daemonStopOnQuitDefaultApplied: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const settings = await createDesktopSettingsStore({ userDataPath }).get();
|
||||||
|
|
||||||
|
expect(settings.notifications.playSound).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps an explicit notification sound choice across restarts", async () => {
|
||||||
|
const userDataPath = await createTempUserDataDir();
|
||||||
|
directories.add(userDataPath);
|
||||||
|
|
||||||
|
await createDesktopSettingsStore({ userDataPath }).patch({
|
||||||
|
notifications: { playSound: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = await createDesktopSettingsStore({ userDataPath }).get();
|
||||||
|
|
||||||
|
expect(settings.notifications.playSound).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not let stale legacy renderer settings override an explicit desktop patch", async () => {
|
it("does not let stale legacy renderer settings override an explicit desktop patch", async () => {
|
||||||
const userDataPath = await createTempUserDataDir();
|
const userDataPath = await createTempUserDataDir();
|
||||||
directories.add(userDataPath);
|
directories.add(userDataPath);
|
||||||
@@ -229,6 +270,7 @@ describe("desktop-settings", () => {
|
|||||||
|
|
||||||
expect(migrated).toEqual({
|
expect(migrated).toEqual({
|
||||||
releaseChannel: "beta",
|
releaseChannel: "beta",
|
||||||
|
notifications: { playSound: true },
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: false,
|
manageBuiltInDaemon: false,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import type { AppReleaseChannel } from "../features/auto-updater.js";
|
|||||||
|
|
||||||
export interface DesktopSettings {
|
export interface DesktopSettings {
|
||||||
releaseChannel: AppReleaseChannel;
|
releaseChannel: AppReleaseChannel;
|
||||||
|
notifications: {
|
||||||
|
playSound: boolean;
|
||||||
|
};
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: boolean;
|
manageBuiltInDaemon: boolean;
|
||||||
keepRunningAfterQuit: boolean;
|
keepRunningAfterQuit: boolean;
|
||||||
@@ -14,6 +17,7 @@ export interface DesktopSettings {
|
|||||||
|
|
||||||
interface DesktopSettingsPatch {
|
interface DesktopSettingsPatch {
|
||||||
releaseChannel?: AppReleaseChannel;
|
releaseChannel?: AppReleaseChannel;
|
||||||
|
notifications?: Partial<DesktopSettings["notifications"]>;
|
||||||
daemon?: Partial<DesktopSettings["daemon"]>;
|
daemon?: Partial<DesktopSettings["daemon"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +42,9 @@ export interface DesktopSettingsStore {
|
|||||||
|
|
||||||
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||||
releaseChannel: "stable",
|
releaseChannel: "stable",
|
||||||
|
notifications: {
|
||||||
|
playSound: true,
|
||||||
|
},
|
||||||
daemon: {
|
daemon: {
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
keepRunningAfterQuit: false,
|
keepRunningAfterQuit: false,
|
||||||
@@ -73,6 +80,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument {
|
|||||||
version: 1,
|
version: 1,
|
||||||
settings: {
|
settings: {
|
||||||
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
|
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
|
||||||
|
notifications: { ...DEFAULT_DESKTOP_SETTINGS.notifications },
|
||||||
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
|
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
|
||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
@@ -85,6 +93,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument {
|
|||||||
function coerceDesktopSettings(input: unknown): DesktopSettings {
|
function coerceDesktopSettings(input: unknown): DesktopSettings {
|
||||||
const result: DesktopSettings = {
|
const result: DesktopSettings = {
|
||||||
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
|
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
|
||||||
|
notifications: { ...DEFAULT_DESKTOP_SETTINGS.notifications },
|
||||||
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
|
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,6 +106,13 @@ function coerceDesktopSettings(input: unknown): DesktopSettings {
|
|||||||
result.releaseChannel = releaseChannel;
|
result.releaseChannel = releaseChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRecord(input.notifications)) {
|
||||||
|
const playSound = coerceBoolean(input.notifications.playSound);
|
||||||
|
if (playSound !== null) {
|
||||||
|
result.notifications.playSound = playSound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isRecord(input.daemon)) {
|
if (isRecord(input.daemon)) {
|
||||||
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
|
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
|
||||||
if (manageBuiltInDaemon !== null) {
|
if (manageBuiltInDaemon !== null) {
|
||||||
@@ -124,6 +140,13 @@ function coerceDesktopSettingsPatch(input: unknown): DesktopSettingsPatch {
|
|||||||
patch.releaseChannel = releaseChannel;
|
patch.releaseChannel = releaseChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRecord(input.notifications)) {
|
||||||
|
const playSound = coerceBoolean(input.notifications.playSound);
|
||||||
|
if (playSound !== null) {
|
||||||
|
patch.notifications = { playSound };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isRecord(input.daemon)) {
|
if (isRecord(input.daemon)) {
|
||||||
const daemonPatch: Partial<DesktopSettings["daemon"]> = {};
|
const daemonPatch: Partial<DesktopSettings["daemon"]> = {};
|
||||||
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
|
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
|
||||||
@@ -169,6 +192,7 @@ function mergeDesktopSettings(
|
|||||||
): DesktopSettings {
|
): DesktopSettings {
|
||||||
return {
|
return {
|
||||||
releaseChannel: patch.releaseChannel ?? current.releaseChannel,
|
releaseChannel: patch.releaseChannel ?? current.releaseChannel,
|
||||||
|
notifications: { ...current.notifications, ...patch.notifications },
|
||||||
daemon: { ...current.daemon, ...patch.daemon },
|
daemon: { ...current.daemon, ...patch.daemon },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user