diff --git a/apps/desktop/src/settings/general/notification-app-options.test.ts b/apps/desktop/src/settings/general/notification-app-options.test.ts new file mode 100644 index 0000000000..6a203b8d68 --- /dev/null +++ b/apps/desktop/src/settings/general/notification-app-options.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "vitest"; + +import { getIgnoredAppOptions } from "./notification-app-options"; + +describe("getIgnoredAppOptions", () => { + test("returns installed app matches for partial searches", () => { + const options = getIgnoredAppOptions({ + allInstalledApps: [ + { id: "us.zoom.xos", name: "Zoom Workplace" }, + { id: "com.tinyspeck.slackmacgap", name: "Slack" }, + ], + ignoredPlatforms: [], + inputValue: "zoom", + defaultIgnoredBundleIds: [], + }); + + expect(options).toEqual([{ id: "us.zoom.xos", name: "Zoom Workplace" }]); + }); + + test("filters out already ignored and default ignored apps", () => { + const options = getIgnoredAppOptions({ + allInstalledApps: [ + { id: "us.zoom.xos", name: "Zoom Workplace" }, + { id: "com.tinyspeck.slackmacgap", name: "Slack" }, + { id: "com.openai.chat", name: "ChatGPT" }, + ], + ignoredPlatforms: ["com.tinyspeck.slackmacgap"], + inputValue: "", + defaultIgnoredBundleIds: ["com.openai.chat"], + }); + + expect(options).toEqual([{ id: "us.zoom.xos", name: "Zoom Workplace" }]); + }); +}); diff --git a/apps/desktop/src/settings/general/notification-app-options.ts b/apps/desktop/src/settings/general/notification-app-options.ts new file mode 100644 index 0000000000..41330ce440 --- /dev/null +++ b/apps/desktop/src/settings/general/notification-app-options.ts @@ -0,0 +1,22 @@ +import type { InstalledApp } from "@hypr/plugin-detect"; + +export function getIgnoredAppOptions({ + allInstalledApps, + ignoredPlatforms, + inputValue, + defaultIgnoredBundleIds, +}: { + allInstalledApps: InstalledApp[] | undefined; + ignoredPlatforms: string[]; + inputValue: string; + defaultIgnoredBundleIds: string[] | undefined; +}) { + return (allInstalledApps ?? []).filter((app) => { + const matchesSearch = app.name + .toLowerCase() + .includes(inputValue.toLowerCase()); + const notAlreadyAdded = !ignoredPlatforms.includes(app.id); + const notDefaultIgnored = !(defaultIgnoredBundleIds ?? []).includes(app.id); + return matchesSearch && notAlreadyAdded && notDefaultIgnored; + }); +} diff --git a/apps/desktop/src/settings/general/notification.tsx b/apps/desktop/src/settings/general/notification.tsx index 7a632724ab..4801adbd62 100644 --- a/apps/desktop/src/settings/general/notification.tsx +++ b/apps/desktop/src/settings/general/notification.tsx @@ -21,6 +21,8 @@ import { import { Switch } from "@hypr/ui/components/ui/switch"; import { cn } from "@hypr/utils"; +import { getIgnoredAppOptions } from "./notification-app-options"; + import { useConfigValues } from "~/shared/config"; import * as settings from "~/store/tinybase/store/settings"; @@ -72,12 +74,7 @@ export function NotificationSettingsView() { return allInstalledApps?.find((a) => a.id === bundleId)?.name ?? bundleId; }; - const nameToBundleId = (name: string) => { - return allInstalledApps?.find((a) => a.name === name)?.id ?? name; - }; - - const isDefaultIgnored = (appName: string) => { - const bundleId = nameToBundleId(appName); + const isDefaultIgnored = (bundleId: string) => { return defaultIgnoredBundleIds?.includes(bundleId) ?? false; }; @@ -121,7 +118,7 @@ export function NotificationSettingsView() { notification_event: configs.notification_event, notification_detect: configs.notification_detect, respect_dnd: configs.respect_dnd, - ignored_platforms: configs.ignored_platforms.map(bundleIdToName), + ignored_platforms: configs.ignored_platforms, mic_active_threshold: configs.mic_active_threshold, }, listeners: { @@ -133,9 +130,7 @@ export function NotificationSettingsView() { handleSetNotificationEvent(value.notification_event); handleSetNotificationDetect(value.notification_detect); handleSetRespectDnd(value.respect_dnd); - handleSetIgnoredPlatforms( - JSON.stringify(value.ignored_platforms.map(nameToBundleId)), - ); + handleSetIgnoredPlatforms(JSON.stringify(value.ignored_platforms)); handleSetMicActiveThreshold(value.mic_active_threshold); }, }); @@ -144,42 +139,33 @@ export function NotificationSettingsView() { configs.notification_event || configs.notification_detect; const ignoredPlatforms = form.getFieldValue("ignored_platforms"); - const installedApps = allInstalledApps?.map((app) => app.name) ?? []; - - const filteredApps = installedApps.filter((app) => { - const matchesSearch = app.toLowerCase().includes(inputValue.toLowerCase()); - const notAlreadyAdded = !ignoredPlatforms.includes(app); - const notDefaultIgnored = !isDefaultIgnored(app); - return matchesSearch && notAlreadyAdded && notDefaultIgnored; + const dropdownOptions = getIgnoredAppOptions({ + allInstalledApps, + ignoredPlatforms, + inputValue, + defaultIgnoredBundleIds, }); - const showCustomOption = - inputValue.trim() && - !filteredApps.some((app) => app.toLowerCase() === inputValue.toLowerCase()); - - const dropdownOptions = showCustomOption - ? [inputValue.trim(), ...filteredApps] - : filteredApps; - - const handleAddIgnoredApp = (appName: string) => { - const trimmedName = appName.trim(); + const handleAddIgnoredApp = (bundleId: string) => { if ( - !trimmedName || - ignoredPlatforms.includes(trimmedName) || - isDefaultIgnored(trimmedName) + !bundleId || + ignoredPlatforms.includes(bundleId) || + isDefaultIgnored(bundleId) ) { return; } - form.setFieldValue("ignored_platforms", [...ignoredPlatforms, trimmedName]); + form.setFieldValue("ignored_platforms", [...ignoredPlatforms, bundleId]); void form.handleSubmit(); setInputValue(""); setShowDropdown(false); setSelectedIndex(0); }; - const handleRemoveIgnoredApp = (app: string) => { - const updated = ignoredPlatforms.filter((a: string) => a !== app); + const handleRemoveIgnoredApp = (bundleId: string) => { + const updated = ignoredPlatforms.filter( + (appId: string) => appId !== bundleId, + ); form.setFieldValue("ignored_platforms", updated); void form.handleSubmit(); }; @@ -187,8 +173,9 @@ export function NotificationSettingsView() { const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && inputValue.trim()) { e.preventDefault(); - if (dropdownOptions.length > 0) { - handleAddIgnoredApp(dropdownOptions[selectedIndex]); + const selectedApp = dropdownOptions[selectedIndex]; + if (selectedApp) { + handleAddIgnoredApp(selectedApp.id); } } else if (e.key === "ArrowDown") { e.preventDefault(); @@ -206,9 +193,9 @@ export function NotificationSettingsView() { !inputValue && ignoredPlatforms.length > 0 ) { - const lastApp = ignoredPlatforms[ignoredPlatforms.length - 1]; - if (!isDefaultIgnored(lastApp)) { - handleRemoveIgnoredApp(lastApp); + const lastBundleId = ignoredPlatforms[ignoredPlatforms.length - 1]; + if (!isDefaultIgnored(lastBundleId)) { + handleRemoveIgnoredApp(lastBundleId); } } }; @@ -317,11 +304,11 @@ export function NotificationSettingsView() { className="flex min-h-[38px] w-full cursor-text flex-wrap items-center gap-2 rounded-md border p-2" onClick={() => inputRef.current?.focus()} > - {ignoredPlatforms.map((app: string) => { - const isDefault = isDefaultIgnored(app); + {ignoredPlatforms.map((bundleId: string) => { + const isDefault = isDefaultIgnored(bundleId); return ( - {app} + {bundleIdToName(bundleId)} {isDefault && ( (default) @@ -343,7 +330,7 @@ export function NotificationSettingsView() { variant="ghost" size="sm" className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent" - onClick={() => handleRemoveIgnoredApp(app)} + onClick={() => handleRemoveIgnoredApp(bundleId)} > @@ -370,10 +357,9 @@ export function NotificationSettingsView() {
{dropdownOptions.map((app, index) => { - const isCustom = showCustomOption && index === 0; return ( ); })} diff --git a/crates/storage/src/vault/path.rs b/crates/storage/src/vault/path.rs index 818376cd5b..514a973160 100644 --- a/crates/storage/src/vault/path.rs +++ b/crates/storage/src/vault/path.rs @@ -365,9 +365,10 @@ mod tests { let global_base = temp.path().to_path_buf(); let default_base = temp.path().join("default"); - let result = resolve_base(&global_base, &default_base); - - assert_eq!(result, default_base); + with_env(VAULT_BASE_ENV_VAR, None, || { + let result = resolve_base(&global_base, &default_base); + assert_eq!(result, default_base); + }); } }