Skip to content

Commit 315bedc

Browse files
committed
Add installed filters for "on request" and "dependency" on installed view
1 parent 857f5ab commit 315bedc

14 files changed

Lines changed: 186 additions & 24 deletions

File tree

backend/brew/list.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func (s *ListService) GetBrewPackages() [][]string {
9797
lines := strings.Split(outputStr, "\n")
9898
var packageNames []string
9999
packageVersions := make(map[string]string)
100+
installReasonByName := make(map[string]string)
100101

101102
for _, line := range lines {
102103
line = strings.TrimSpace(line)
@@ -114,11 +115,44 @@ func (s *ListService) GetBrewPackages() [][]string {
114115
}
115116
}
116117

118+
// Resolve install origin (on request vs as dependency) from Homebrew JSON metadata.
119+
infoOutput, infoErr := s.executor.Run("info", "--json=v2", "--formula", "--installed")
120+
if infoErr == nil {
121+
var info struct {
122+
Formulae []struct {
123+
Name string `json:"name"`
124+
Installed []struct {
125+
InstalledOnRequest bool `json:"installed_on_request"`
126+
InstalledAsDependency bool `json:"installed_as_dependency"`
127+
} `json:"installed"`
128+
} `json:"formulae"`
129+
}
130+
131+
if err := json.Unmarshal(infoOutput, &info); err == nil {
132+
for _, f := range info.Formulae {
133+
reason := "unknown"
134+
if len(f.Installed) > 0 {
135+
switch {
136+
case f.Installed[0].InstalledOnRequest:
137+
reason = "on_request"
138+
case f.Installed[0].InstalledAsDependency:
139+
reason = "dependency"
140+
}
141+
}
142+
installReasonByName[f.Name] = reason
143+
}
144+
}
145+
}
146+
117147
// Build result with name, version, and empty size (lazy loaded)
118148
var packages [][]string
119149
for _, name := range packageNames {
120150
version := packageVersions[name]
121-
packages = append(packages, []string{name, version, ""})
151+
reason := installReasonByName[name]
152+
if reason == "" {
153+
reason = "unknown"
154+
}
155+
packages = append(packages, []string{name, version, "", reason})
122156
}
123157

124158
return packages

frontend/src/App.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,37 @@ body {
18221822
transform: translateY(-1px);
18231823
}
18241824

1825+
.installed-filter-pills-row {
1826+
display: flex;
1827+
align-items: center;
1828+
gap: 8px;
1829+
margin: -2px 0 10px 0;
1830+
}
1831+
1832+
.installed-filter-pill {
1833+
background: rgba(255, 255, 255, 0.04);
1834+
border: 1px solid var(--glass-border);
1835+
color: var(--text-secondary);
1836+
padding: 0.22rem 0.62rem;
1837+
border-radius: 999px;
1838+
cursor: pointer;
1839+
font-size: 0.75rem;
1840+
font-weight: 600;
1841+
transition: all var(--transition);
1842+
white-space: nowrap;
1843+
}
1844+
1845+
.installed-filter-pill:hover {
1846+
border-color: var(--glass-border-strong);
1847+
color: var(--text-main);
1848+
}
1849+
1850+
.installed-filter-pill.active {
1851+
background: rgba(76, 175, 80, 0.14);
1852+
border-color: rgba(76, 175, 80, 0.45);
1853+
color: #7bcf83;
1854+
}
1855+
18251856
.confirm-overlay {
18261857
position: fixed;
18271858
top: 0;

frontend/src/App.tsx

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { mapToSupportedLanguage } from "./i18n/languageUtils";
6565
interface PackageEntry {
6666
name: string;
6767
installedVersion: string;
68+
installReason?: "on_request" | "dependency" | "unknown";
6869
latestVersion?: string;
6970
size?: string;
7071
desc?: string;
@@ -133,6 +134,7 @@ const WailBrewApp = () => {
133134
const [deprecatedFormulae, setDeprecatedFormulae] = useState<string[]>([]);
134135
const [selectedDeprecatedPackage, setSelectedDeprecatedPackage] = useState<PackageEntry | null>(null);
135136
const [updatableError, setUpdatableError] = useState<string>("");
137+
const [installedFilter, setInstalledFilter] = useState<"all" | "on_request" | "dependency">("all");
136138
const [homebrewLog, setHomebrewLog] = useState<string>("");
137139
const [homebrewVersion, setHomebrewVersion] = useState<string>("");
138140
const [homebrewUpdateStatus, setHomebrewUpdateStatus] = useState<{ isUpToDate: boolean | null, latestVersion: string | null }>({ isUpToDate: null, latestVersion: null });
@@ -237,10 +239,11 @@ const WailBrewApp = () => {
237239
throw new Error(`${t('errors.failedRepositories')}: ${safeRepos[0][1]}`);
238240
}
239241

240-
const installedFormatted = safeInstalled.map(([name, installedVersion, size]) => ({
242+
const installedFormatted = safeInstalled.map(([name, installedVersion, size, installReason]) => ({
241243
name,
242244
installedVersion,
243245
size,
246+
installReason: (installReason as "on_request" | "dependency" | "unknown") || "unknown",
244247
isInstalled: true,
245248
}));
246249
const casksFormatted = safeInstalledCasks.map(([name, installedVersion, size]) => ({
@@ -1084,13 +1087,22 @@ const WailBrewApp = () => {
10841087
const activePackages = getActivePackages();
10851088
const activeRepositories = getActiveRepositories();
10861089

1087-
const filteredPackages = activePackages.filter((pkg) =>
1090+
const installedFilteredPackages = view === "installed"
1091+
? activePackages.filter((pkg) => {
1092+
if (installedFilter === "all") return true;
1093+
return pkg.installReason === installedFilter;
1094+
})
1095+
: activePackages;
1096+
1097+
const filteredPackages = installedFilteredPackages.filter((pkg) =>
10881098
pkg.name.toLowerCase().includes(searchQuery.toLowerCase())
10891099
);
10901100

10911101
const filteredRepositories = activeRepositories.filter((repo) =>
10921102
repo.name.toLowerCase().includes(searchQuery.toLowerCase())
10931103
);
1104+
const installedOnRequestCount = packages.filter(pkg => pkg.installReason === "on_request").length;
1105+
const installedDependencyCount = packages.filter(pkg => pkg.installReason === "dependency").length;
10941106

10951107
const handleSelect = async (pkg: PackageEntry) => {
10961108
setSelectedPackage(pkg);
@@ -2006,10 +2018,11 @@ const WailBrewApp = () => {
20062018
setError(safeInstalled[0][1]);
20072019
setPackages([]);
20082020
} else {
2009-
const formatted = safeInstalled.map(([name, installedVersion, size]) => ({
2021+
const formatted = safeInstalled.map(([name, installedVersion, size, installReason]) => ({
20102022
name,
20112023
installedVersion,
20122024
size,
2025+
installReason: (installReason as "on_request" | "dependency" | "unknown") || "unknown",
20132026
isInstalled: true,
20142027
}));
20152028
setPackages(formatted);
@@ -2178,19 +2191,37 @@ const WailBrewApp = () => {
21782191
<HeaderRow
21792192
title={t('headers.installedFormulas', { count: packages.length })}
21802193
actions={
2181-
<button
2182-
className="refresh-button"
2183-
onClick={handleRefreshPackages}
2184-
disabled={loading}
2185-
title={t('buttons.refresh')}
2186-
>
2187-
<RefreshCw size={18} />
2188-
</button>
2194+
<>
2195+
<button
2196+
className="refresh-button"
2197+
onClick={handleRefreshPackages}
2198+
disabled={loading}
2199+
title={t('buttons.refresh')}
2200+
>
2201+
<RefreshCw size={18} />
2202+
</button>
2203+
</>
21892204
}
21902205
searchQuery={searchQuery}
21912206
onSearchChange={setSearchQuery}
21922207
onClearSearch={() => setSearchQuery("")}
21932208
/>
2209+
<div className="installed-filter-pills-row">
2210+
<button
2211+
className={`installed-filter-pill ${installedFilter === "on_request" ? "active" : ""}`}
2212+
onClick={() => setInstalledFilter(prev => prev === "on_request" ? "all" : "on_request")}
2213+
title={t('filters.toggleOnRequest')}
2214+
>
2215+
{t('filters.onRequest', { count: installedOnRequestCount })}
2216+
</button>
2217+
<button
2218+
className={`installed-filter-pill ${installedFilter === "dependency" ? "active" : ""}`}
2219+
onClick={() => setInstalledFilter(prev => prev === "dependency" ? "all" : "dependency")}
2220+
title={t('filters.toggleAsDependency')}
2221+
>
2222+
{t('filters.asDependency', { count: installedDependencyCount })}
2223+
</button>
2224+
</div>
21942225
{error && <div className="result error">{error}</div>}
21952226
<PackageTable
21962227
ref={view === "installed" ? packageTableRef : null}
@@ -2235,7 +2266,7 @@ const WailBrewApp = () => {
22352266
onSearchChange={setSearchQuery}
22362267
onClearSearch={() => setSearchQuery("")}
22372268
/>
2238-
{updatableError && <div className="result error">{updatableError}</div>}
2269+
{error && <div className="result error">{error}</div>}
22392270
<PackageTable
22402271
ref={view === "casks" ? packageTableRef : null}
22412272
packages={filteredPackages}

frontend/src/i18n/locales/de.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "Veraltete Formel \"{{name}}\" deinstallieren",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "Auf Anfrage ({{count}})",
129+
"asDependency": "Als Abhängigkeit ({{count}})",
130+
"toggleOnRequest": "Filter umschalten: auf Anfrage installiert",
131+
"toggleAsDependency": "Filter umschalten: als Abhängigkeit installiert"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} von {{total}} ausgewählt"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "Uninstall deprecated formula \"{{name}}\"",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "On Request ({{count}})",
129+
"asDependency": "As Dependency ({{count}})",
130+
"toggleOnRequest": "Toggle filter: installed on request",
131+
"toggleAsDependency": "Toggle filter: installed as dependency"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} of {{total}} selected"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/es.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "Desinstalar formula en desuso \"{{name}}\"",
125125
"untap": "Remover \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "Por petición ({{count}})",
129+
"asDependency": "Como dependencia ({{count}})",
130+
"toggleOnRequest": "Alternar filtro: instalado por petición",
131+
"toggleAsDependency": "Alternar filtro: instalado como dependencia"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} de {{total}} seleccionados"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Ajustes"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/fr.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "Désinstaller la formule obsolète \"{{name}}\"",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "À la demande ({{count}})",
129+
"asDependency": "Comme dépendance ({{count}})",
130+
"toggleOnRequest": "Basculer le filtre : installé à la demande",
131+
"toggleAsDependency": "Basculer le filtre : installé comme dépendance"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} sur {{total}} sélectionné(s)"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/he.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "הסר נוסחה שהוצאה משימוש \"{{name}}\"",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "לפי בקשה ({{count}})",
129+
"asDependency": "כתלות ({{count}})",
130+
"toggleOnRequest": "החלף מסנן: הותקן לפי בקשה",
131+
"toggleAsDependency": "החלף מסנן: הותקן כתלות"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} מתוך {{total}} נבחרו"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/ko.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "지원 중단된 Formula \"{{name}}\" 제거",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "요청 설치 ({{count}})",
129+
"asDependency": "의존성 설치 ({{count}})",
130+
"toggleOnRequest": "필터 전환: 요청으로 설치됨",
131+
"toggleAsDependency": "필터 전환: 의존성으로 설치됨"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{total}}개 중 {{count}}개 선택됨"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

frontend/src/i18n/locales/pt_BR.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@
124124
"uninstallDeprecated": "Desinstalar fórmula descontinuada \"{{name}}\"",
125125
"untap": "Untap \"{{name}}\""
126126
},
127+
"filters": {
128+
"onRequest": "Por solicitação ({{count}})",
129+
"asDependency": "Como dependência ({{count}})",
130+
"toggleOnRequest": "Alternar filtro: instalado por solicitação",
131+
"toggleAsDependency": "Alternar filtro: instalado como dependência"
132+
},
127133
"multiSelect": {
128134
"selectedCount": "{{count}} de {{total}} selecionado(s)"
129135
},
@@ -584,4 +590,4 @@
584590
"view": {
585591
"settings": "Settings"
586592
}
587-
}
593+
}

0 commit comments

Comments
 (0)