|
14 | 14 | base_preset: string; |
15 | 15 | is_public: number; |
16 | 16 | alias: string | null; |
17 | | - packages?: string[]; |
| 17 | + packages?: any[]; |
18 | 18 | custom_script?: string; |
19 | 19 | dotfiles_repo?: string; |
20 | 20 | updated_at?: string; |
|
47 | 47 | type: 'formula' | 'cask' | 'tap'; |
48 | 48 | } |
49 | 49 |
|
50 | | - let selectedPackages = $state(new Set<string>()); |
| 50 | + let selectedPackages = $state(new Map<string, string>()); |
51 | 51 | let presetExpanded = $state(false); |
52 | 52 | let packageSearch = $state(''); |
53 | 53 | let searchResults = $state<SearchResult[]>([]); |
|
113 | 113 |
|
114 | 114 | function getExtraPackages(): string[] { |
115 | 115 | const presetPkgs = new Set(getPresetPackages(formData.base_preset)); |
116 | | - return Array.from(selectedPackages).filter((pkg) => !presetPkgs.has(pkg)); |
| 116 | + return Array.from(selectedPackages.keys()).filter((pkg) => !presetPkgs.has(pkg)); |
117 | 117 | } |
118 | 118 |
|
119 | 119 |
|
120 | 120 |
|
121 | 121 | function initPackagesForPreset(preset: string) { |
122 | 122 | const presetPkgs = getPresetPackages(preset); |
123 | | - selectedPackages = new Set(presetPkgs); |
| 123 | + const newMap = new Map<string, string>(); |
| 124 | + for (const pkg of presetPkgs) { |
| 125 | + newMap.set(pkg, 'formula'); |
| 126 | + } |
| 127 | + selectedPackages = newMap; |
124 | 128 | } |
125 | 129 |
|
126 | 130 | function handlePresetChange(newPreset: string) { |
|
129 | 133 | } |
130 | 134 |
|
131 | 135 | function togglePresetPackage(pkg: string) { |
132 | | - const newSet = new Set(selectedPackages); |
133 | | - if (newSet.has(pkg)) { |
134 | | - newSet.delete(pkg); |
| 136 | + const newMap = new Map(selectedPackages); |
| 137 | + if (newMap.has(pkg)) { |
| 138 | + newMap.delete(pkg); |
135 | 139 | } else { |
136 | | - newSet.add(pkg); |
| 140 | + newMap.set(pkg, 'formula'); |
137 | 141 | } |
138 | | - selectedPackages = newSet; |
| 142 | + selectedPackages = newMap; |
139 | 143 | } |
140 | 144 |
|
141 | 145 | onMount(async () => { |
|
175 | 179 | custom_script: config.custom_script || '', |
176 | 180 | dotfiles_repo: config.dotfiles_repo || '' |
177 | 181 | }; |
178 | | - const savedPkgs = config.packages || []; |
179 | | - if (savedPkgs.length > 0) { |
180 | | - selectedPackages = new Set(savedPkgs); |
181 | | - } else { |
182 | | - initPackagesForPreset(config.base_preset); |
| 182 | + const savedPkgs = config.packages || []; |
| 183 | + if (savedPkgs.length > 0) { |
| 184 | + const newMap = new Map<string, string>(); |
| 185 | + for (const pkg of savedPkgs) { |
| 186 | + if (typeof pkg === 'string') { |
| 187 | + newMap.set(pkg, 'formula'); |
| 188 | + } else { |
| 189 | + newMap.set((pkg as any).name, (pkg as any).type || 'formula'); |
| 190 | + } |
183 | 191 | } |
| 192 | + selectedPackages = newMap; |
| 193 | + } else { |
| 194 | + initPackagesForPreset(config.base_preset); |
| 195 | + } |
184 | 196 | } else { |
185 | 197 | editingSlug = ''; |
186 | 198 | formData = { |
|
203 | 215 | showModal = false; |
204 | 216 | } |
205 | 217 |
|
206 | | - function togglePackage(pkg: string) { |
207 | | - const newSet = new Set(selectedPackages); |
208 | | - if (newSet.has(pkg)) { |
209 | | - newSet.delete(pkg); |
| 218 | + function togglePackage(pkg: string, type: string = 'formula') { |
| 219 | + const newMap = new Map(selectedPackages); |
| 220 | + if (newMap.has(pkg)) { |
| 221 | + newMap.delete(pkg); |
210 | 222 | } else { |
211 | | - newSet.add(pkg); |
| 223 | + newMap.set(pkg, type); |
212 | 224 | } |
213 | | - selectedPackages = newSet; |
214 | | - formData.packages = Array.from(newSet); |
| 225 | + selectedPackages = newMap; |
| 226 | + formData.packages = Array.from(newMap.keys()); |
215 | 227 | } |
216 | 228 |
|
217 | 229 | async function saveConfig() { |
|
233 | 245 | body: JSON.stringify({ |
234 | 246 | ...formData, |
235 | 247 | alias: formData.alias.trim() || null, |
236 | | - packages: Array.from(selectedPackages) |
| 248 | + packages: Array.from(selectedPackages.entries()).map(([name, type]) => ({ name, type })) |
237 | 249 | }) |
238 | 250 | }); |
239 | 251 |
|
|
347 | 359 | custom_script: '', |
348 | 360 | dotfiles_repo: '' |
349 | 361 | }; |
350 | | - selectedPackages = new Set(data.packages); |
| 362 | + const importMap = new Map<string, string>(); |
| 363 | + for (const pkg of data.packages) { |
| 364 | + importMap.set(pkg, 'formula'); |
| 365 | + } |
| 366 | + selectedPackages = importMap; |
351 | 367 | showModal = true; |
352 | 368 | } catch (e) { |
353 | 369 | importError = 'Failed to parse Brewfile'; |
|
520 | 536 | </div> |
521 | 537 | {#if getExtraPackages().length > 0} |
522 | 538 | <div class="selected-extras"> |
523 | | - {#each getExtraPackages() as pkg} |
524 | | - <button type="button" class="extra-tag" onclick={() => togglePackage(pkg)}> |
525 | | - {pkg} |
526 | | - <span class="remove-icon">×</span> |
527 | | - </button> |
528 | | - {/each} |
| 539 | + {#each getExtraPackages() as pkg} |
| 540 | + <button type="button" class="extra-tag" onclick={() => togglePackage(pkg, selectedPackages.get(pkg) || 'formula')}> |
| 541 | + {pkg} |
| 542 | + {#if selectedPackages.get(pkg) === 'cask'} |
| 543 | + <span class="type-badge cask">cask</span> |
| 544 | + {:else if selectedPackages.get(pkg) === 'tap'} |
| 545 | + <span class="type-badge tap">tap</span> |
| 546 | + {/if} |
| 547 | + <span class="remove-icon">×</span> |
| 548 | + </button> |
| 549 | + {/each} |
529 | 550 | </div> |
530 | 551 | {/if} |
531 | 552 | <div class="packages-search"> |
|
544 | 565 | {:else if packageSearch.length >= 2} |
545 | 566 | <div class="packages-grid"> |
546 | 567 | {#each searchResults as result} |
547 | | - <button type="button" class="package-item" class:selected={selectedPackages.has(result.name)} onclick={() => togglePackage(result.name)}> |
| 568 | + <button type="button" class="package-item" class:selected={selectedPackages.has(result.name)} onclick={() => togglePackage(result.name, result.type)}> |
548 | 569 | <span class="check-indicator">{selectedPackages.has(result.name) ? '✓' : ''}</span> |
549 | 570 | <div class="package-content"> |
550 | 571 | <div class="package-info"> |
|
1090 | 1111 | background: #1a9f4a; |
1091 | 1112 | } |
1092 | 1113 |
|
| 1114 | + .type-badge { |
| 1115 | + font-size: 0.6rem; |
| 1116 | + padding: 1px 4px; |
| 1117 | + border-radius: 3px; |
| 1118 | + text-transform: uppercase; |
| 1119 | + font-family: 'JetBrains Mono', monospace; |
| 1120 | + } |
| 1121 | +
|
| 1122 | + .type-badge.cask { |
| 1123 | + background: rgba(96, 165, 250, 0.2); |
| 1124 | + color: #60a5fa; |
| 1125 | + } |
| 1126 | +
|
| 1127 | + .type-badge.tap { |
| 1128 | + background: rgba(251, 191, 36, 0.2); |
| 1129 | + color: #fbbf24; |
| 1130 | + } |
| 1131 | +
|
1093 | 1132 | .remove-icon { |
1094 | 1133 | font-size: 0.9rem; |
1095 | 1134 | font-weight: bold; |
|
0 commit comments