Skip to content

Commit db7de38

Browse files
committed
Fix Modrinth UX and update handling
- replace placeholder icons with project SVGs\n- add uninstall + auto-refresh for Modrinth updates list\n- align custom mods section with Mods tab\n- use shared HTTP user agent\n- keep lockfile in sync
1 parent 9b697bb commit db7de38

9 files changed

Lines changed: 167 additions & 97 deletions

File tree

bun.lock

Lines changed: 41 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/img/icon/icon-download.svg

Lines changed: 5 additions & 0 deletions
Loading

public/img/icon/icon-refresh.svg

Lines changed: 6 additions & 0 deletions
Loading

public/img/icon/icon-trash.svg

Lines changed: 7 additions & 0 deletions
Loading

src-tauri/src/app/modrinth.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ pub async fn search_mods(query: &str, mc_version: &str, loader: &str) -> Result<
7474

7575
let response: SearchResponse = HTTP_CLIENT
7676
.get(&url)
77-
.header("User-Agent", "LiquidLauncher/0.5.0")
7877
.send()
7978
.await?
8079
.json()
@@ -91,7 +90,6 @@ pub async fn get_compatible_version(project_id: &str, mc_version: &str, loader:
9190

9291
let versions: Vec<ModrinthVersion> = HTTP_CLIENT
9392
.get(&url)
94-
.header("User-Agent", "LiquidLauncher/0.5.0")
9593
.send()
9694
.await?
9795
.json()
@@ -109,7 +107,6 @@ pub async fn download_mod(file: &ModrinthFile, dest_path: &std::path::Path) -> R
109107

110108
let response = HTTP_CLIENT
111109
.get(&file.url)
112-
.header("User-Agent", "LiquidLauncher/0.5.0")
113110
.timeout(std::time::Duration::from_secs(300))
114111
.send()
115112
.await
@@ -156,7 +153,6 @@ pub async fn get_project_from_hash(hash: &str) -> Result<Option<String>> {
156153

157154
let response = HTTP_CLIENT
158155
.get(&url)
159-
.header("User-Agent", "LiquidLauncher/0.5.0")
160156
.send()
161157
.await;
162158

@@ -175,7 +171,6 @@ pub async fn get_version_from_hash(hash: &str) -> Result<Option<ModrinthVersion>
175171

176172
let response = HTTP_CLIENT
177173
.get(&url)
178-
.header("User-Agent", "LiquidLauncher/0.5.0")
179174
.send()
180175
.await;
181176

@@ -193,7 +188,6 @@ pub async fn get_project(project_id: &str) -> Result<Option<ModrinthProjectDetai
193188

194189
let response = HTTP_CLIENT
195190
.get(&url)
196-
.header("User-Agent", "LiquidLauncher/0.5.0")
197191
.send()
198192
.await;
199193

src/lib/main/MainScreen.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@
190190
const updates = await invoke("modrinth_check_updates", {
191191
branch: versionState.currentBuild.branch,
192192
mcVersion: versionState.currentBuild.mcVersion,
193-
loader: "fabric"
193+
loader: versionState.currentBuild.subsystem || "fabric"
194194
});
195195
196196
const modsToUpdate = updates.filter(m => m.has_update);
@@ -201,7 +201,7 @@
201201
await invoke("modrinth_update_mod", {
202202
projectId: mod.info.project_id,
203203
mcVersion: versionState.currentBuild.mcVersion,
204-
loader: "fabric",
204+
loader: versionState.currentBuild.subsystem || "fabric",
205205
branch: versionState.currentBuild.branch
206206
});
207207
}

src/lib/main/ModrinthSearch.svelte

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,6 @@
9090
if (e.key === "Enter") search();
9191
}
9292
93-
/**
94-
* Get the current state of a mod for UI rendering
95-
* Priority: installing > installed > idle
96-
* Uses reactive isInstalledMap for automatic updates
97-
*/
98-
function getState(projectId) {
99-
if (installing[projectId]) return 'installing';
100-
return isInstalledMap[projectId] ? 'installed' : 'idle';
101-
}
10293
</script>
10394

10495
<div class="modrinth-search">

src/lib/main/ModrinthUpdates.svelte

Lines changed: 93 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { createEventDispatcher, onMount } from "svelte";
2+
import { createEventDispatcher } from "svelte";
33
import { invoke } from "@tauri-apps/api/core";
44
import { scale, fade } from "svelte/transition";
55
@@ -10,6 +10,8 @@
1010
let mods = [];
1111
let checking = false;
1212
let updating = {};
13+
let removing = {};
14+
let lastKey = "";
1315
1416
const dispatch = createEventDispatcher();
1517
@@ -40,8 +42,7 @@
4042
}
4143
4244
async function updateMod(mod) {
43-
updating[mod.info.project_id] = true;
44-
updating = updating;
45+
updating = { ...updating, [mod.info.project_id]: true };
4546
4647
try {
4748
await invoke("modrinth_update_mod", {
@@ -58,8 +59,23 @@
5859
console.error("Update failed:", e);
5960
}
6061
61-
updating[mod.info.project_id] = false;
62-
updating = updating;
62+
updating = { ...updating, [mod.info.project_id]: false };
63+
}
64+
65+
async function uninstallMod(mod) {
66+
removing = { ...removing, [mod.info.project_id]: true };
67+
try {
68+
await invoke("delete_custom_mod", {
69+
branch,
70+
mcVersion,
71+
modName: mod.info.filename
72+
});
73+
await checkUpdates();
74+
dispatch("removed");
75+
} catch (e) {
76+
console.error("Uninstall failed:", e);
77+
}
78+
removing = { ...removing, [mod.info.project_id]: false };
6379
}
6480
6581
async function updateAll() {
@@ -72,7 +88,13 @@
7288
$: hasUpdates = mods.some(m => m.has_update);
7389
$: updateCount = mods.filter(m => m.has_update).length;
7490
75-
onMount(checkUpdates);
91+
$: if (mcVersion && branch && loader) {
92+
const nextKey = `${branch}:${mcVersion}:${loader}`;
93+
if (nextKey !== lastKey) {
94+
lastKey = nextKey;
95+
checkUpdates();
96+
}
97+
}
7698
</script>
7799

78100
{#if mods.length > 0}
@@ -95,19 +117,16 @@
95117
Update All
96118
</button>
97119
{/if}
98-
<button
99-
class="refresh-btn"
100-
on:click={checkUpdates}
120+
<button
121+
class="refresh-btn"
122+
on:click={checkUpdates}
101123
disabled={checking}
102124
aria-label="Refresh updates"
103125
>
104126
{#if checking}
105127
<span class="spinner"></span>
106128
{:else}
107-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
108-
<path d="M1 4v6h6M23 20v-6h-6"/>
109-
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/>
110-
</svg>
129+
<img class="icon" src="img/icon/icon-refresh.svg" alt="" />
111130
{/if}
112131
</button>
113132
</div>
@@ -117,29 +136,39 @@
117136
{#each mods as mod (mod.info.project_id)}
118137
<div class="mod-row" class:has-update={mod.has_update}>
119138
<span class="mod-name">{mod.info.title}</span>
120-
{#if mod.has_update}
121-
<span class="new-version" in:fade={{ duration: 150 }}>
122-
→ {mod.new_version}
123-
</span>
124-
<button
125-
class="update-btn"
126-
on:click={() => updateMod(mod)}
127-
disabled={updating[mod.info.project_id]}
128-
aria-label={`Update ${mod.info.title} to version ${mod.new_version}`}
139+
<div class="mod-actions">
140+
{#if mod.has_update}
141+
<span class="new-version" in:fade={{ duration: 150 }}>
142+
→ {mod.new_version}
143+
</span>
144+
<button
145+
class="icon-btn update-btn"
146+
on:click={() => updateMod(mod)}
147+
disabled={updating[mod.info.project_id]}
148+
aria-label={`Update ${mod.info.title} to version ${mod.new_version}`}
149+
>
150+
{#if updating[mod.info.project_id]}
151+
<span class="spinner small"></span>
152+
{:else}
153+
<img class="icon" src="img/icon/icon-download.svg" alt="" />
154+
{/if}
155+
</button>
156+
{:else}
157+
<span class="up-to-date">✓</span>
158+
{/if}
159+
<button
160+
class="icon-btn delete-btn"
161+
on:click={() => uninstallMod(mod)}
162+
disabled={removing[mod.info.project_id]}
163+
aria-label={`Uninstall ${mod.info.title}`}
129164
>
130-
{#if updating[mod.info.project_id]}
165+
{#if removing[mod.info.project_id]}
131166
<span class="spinner small"></span>
132167
{:else}
133-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
134-
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
135-
<polyline points="7 10 12 15 17 10"/>
136-
<line x1="12" y1="15" x2="12" y2="3"/>
137-
</svg>
168+
<img class="icon" src="img/icon/icon-trash.svg" alt="" />
138169
{/if}
139170
</button>
140-
{:else}
141-
<span class="up-to-date">✓</span>
142-
{/if}
171+
</div>
143172
</div>
144173
{/each}
145174
</div>
@@ -224,7 +253,7 @@
224253
opacity: 0.5;
225254
}
226255
227-
.refresh-btn svg {
256+
.refresh-btn .icon {
228257
width: 14px;
229258
height: 14px;
230259
color: #888;
@@ -261,6 +290,12 @@
261290
text-overflow: ellipsis;
262291
}
263292
293+
.mod-actions {
294+
display: flex;
295+
align-items: center;
296+
gap: 8px;
297+
}
298+
264299
.new-version {
265300
color: #4677ff;
266301
font-size: 11px;
@@ -272,8 +307,7 @@
272307
font-size: 12px;
273308
}
274309
275-
.update-btn {
276-
background: #4677ff;
310+
.icon-btn {
277311
border: none;
278312
border-radius: 4px;
279313
width: 28px;
@@ -285,21 +319,44 @@
285319
transition: all 0.2s;
286320
}
287321
322+
.update-btn {
323+
background: #4677ff;
324+
}
325+
288326
.update-btn:hover:not(:disabled) {
289327
background: #5a88ff;
290328
transform: scale(1.05);
291329
}
292330
293-
.update-btn:disabled {
331+
.delete-btn {
332+
background: #3f6dff;
333+
box-shadow: 0 0 0 2px rgba(70, 119, 255, 0.85), 0 0 18px rgba(70, 119, 255, 0.9);
334+
}
335+
336+
.delete-btn:hover:not(:disabled) {
337+
background: #e55252;
338+
transform: scale(1.14);
339+
box-shadow: 0 0 0 2px rgba(229, 82, 82, 0.35), 0 6px 14px rgba(229, 82, 82, 0.35);
340+
animation: trash-wiggle 0.22s ease-in-out 1;
341+
}
342+
343+
.icon-btn:disabled {
294344
opacity: 0.7;
295345
}
296346
297-
.update-btn svg {
347+
.icon-btn .icon {
298348
width: 14px;
299349
height: 14px;
300350
color: white;
301351
}
302352
353+
@keyframes trash-wiggle {
354+
0% { transform: scale(1.06) rotate(0deg); }
355+
35% { transform: scale(1.06) rotate(-6deg); }
356+
70% { transform: scale(1.06) rotate(6deg); }
357+
100% { transform: scale(1.06) rotate(0deg); }
358+
}
359+
303360
.spinner {
304361
width: 14px;
305362
height: 14px;

src/lib/main/VersionSelect.svelte

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
};
2424
2525
let activeTab = "Version";
26+
let modrinthUpdatesRef;
2627
const dispatch = createEventDispatcher();
2728
2829
// Sync store with versionState.customMods whenever it changes
@@ -69,6 +70,13 @@
6970
alert(`Failed to install mod: ${error}`);
7071
}
7172
}
73+
74+
function handleModrinthInstalled() {
75+
dispatch('updateMods');
76+
if (modrinthUpdatesRef?.checkUpdates) {
77+
modrinthUpdatesRef.checkUpdates();
78+
}
79+
}
7280
</script>
7381
7482
<SettingsContainer
@@ -119,7 +127,8 @@
119127
/>
120128
{/each}
121129
</SettingWrapper>
122-
<SettingWrapper title={`Additional mods - ${versionState.currentBuild?.subsystem ? `${versionState.currentBuild.subsystem.charAt(0).toUpperCase()}${versionState.currentBuild.subsystem.slice(1)}` : ''} ${versionState.currentBuild?.mcVersion}`}>
130+
{:else if activeTab === "Mods"}
131+
<SettingWrapper title={`Custom mods - ${versionState.currentBuild?.subsystem ? `${versionState.currentBuild.subsystem.charAt(0).toUpperCase()}${versionState.currentBuild.subsystem.slice(1)}` : ''} ${versionState.currentBuild?.mcVersion}`}>
123132
<div slot="title-element">
124133
<IconButtonSetting
125134
text="Install"
@@ -136,18 +145,19 @@
136145
/>
137146
{/each}
138147
</SettingWrapper>
139-
{:else if activeTab === "Mods"}
140148
<ModrinthSearch
141149
mcVersion={versionState.currentBuild?.mcVersion || ""}
142150
loader={versionState.currentBuild?.subsystem || "fabric"}
143151
branch={versionState.currentBuild?.branch || ""}
144-
on:installed={() => dispatch('updateMods')}
152+
on:installed={handleModrinthInstalled}
145153
/>
146154
<ModrinthUpdates
155+
bind:this={modrinthUpdatesRef}
147156
mcVersion={versionState.currentBuild?.mcVersion || ""}
148157
loader={versionState.currentBuild?.subsystem || "fabric"}
149158
branch={versionState.currentBuild?.branch || ""}
150159
on:updated={() => dispatch('updateMods')}
160+
on:removed={() => dispatch('updateMods')}
151161
/>
152162
{/if}
153163
</SettingsContainer>

0 commit comments

Comments
 (0)