Fix: static path (META-INF/resources), settings @Transactional, search dropdown position, async follow + feed auto-refresh

This commit is contained in:
opencode
2026-07-15 10:46:22 +02:00
parent 292de62198
commit 2078a56fc2
14 changed files with 64 additions and 36 deletions
@@ -29,8 +29,10 @@ public class AppSetting extends PanacheEntity {
if (s == null) { if (s == null) {
s = new AppSetting(); s = new AppSetting();
s.key = key; s.key = key;
s.value = value;
s.persist(); s.persist();
} else {
s.value = value;
} }
s.value = value;
} }
} }
@@ -19,6 +19,7 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam; import jakarta.ws.rs.PathParam;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
@Path("/artists") @Path("/artists")
public class ArtistResource { public class ArtistResource {
@@ -36,6 +37,10 @@ public class ArtistResource {
@Location("fragments/feed-list.html") @Location("fragments/feed-list.html")
Template fragments_feed_list; Template fragments_feed_list;
@Inject
@Location("fragments/feed-row.html")
Template fragments_feed_row;
@Inject @Inject
@Location("fragments/artist-row.html") @Location("fragments/artist-row.html")
Template fragments_artist_row; Template fragments_artist_row;
@@ -61,16 +66,16 @@ public class ArtistResource {
dto.area = new ArtistSearchResult.ArtistDto.Area(); dto.area = new ArtistSearchResult.ArtistDto.Area();
dto.area.name = area; dto.area.name = area;
} }
FollowedArtist artist = artistService.follow(dto); artistService.follow(dto);
syncService.syncArtist(artist.mbid); CompletableFuture.runAsync(() -> syncService.syncArtist(mbid));
return feedFragment(0); return feedFragment(0, true);
} }
@DELETE @DELETE
@Path("/{mbid}") @Path("/{mbid}")
public TemplateInstance unfollow(@PathParam("mbid") String mbid) { public TemplateInstance unfollow(@PathParam("mbid") String mbid) {
artistService.unfollow(mbid); artistService.unfollow(mbid);
return feedFragment(0); return feedFragment(0, false);
} }
@GET @GET
@@ -96,12 +101,12 @@ public class ArtistResource {
FollowedArtist artist = artistService.findArtist(mbid); FollowedArtist artist = artistService.findArtist(mbid);
if (artist != null) { if (artist != null) {
artistService.setTypeEnabled(artist, primaryType, enabled); artistService.setTypeEnabled(artist, primaryType, enabled);
syncService.resyncArtist(mbid); CompletableFuture.runAsync(() -> syncService.resyncArtist(mbid));
} }
return feedFragment(0); return feedFragment(0, true);
} }
private TemplateInstance feedFragment(int offset) { private TemplateInstance feedFragment(int offset, boolean autoRefresh) {
if (offset < 0) { if (offset < 0) {
offset = 0; offset = 0;
} }
@@ -111,6 +116,7 @@ public class ArtistResource {
return fragments_feed_list.data("rows", rows) return fragments_feed_list.data("rows", rows)
.data("nextOffset", nextOffset) .data("nextOffset", nextOffset)
.data("hasMore", hasMore) .data("hasMore", hasMore)
.data("pageSize", feedService.pageSize()); .data("pageSize", feedService.pageSize())
.data("autoRefresh", autoRefresh);
} }
} }
@@ -40,7 +40,8 @@ public class PageResource {
return fragments_feed_list.data("rows", rows) return fragments_feed_list.data("rows", rows)
.data("nextOffset", nextOffset) .data("nextOffset", nextOffset)
.data("hasMore", hasMore) .data("hasMore", hasMore)
.data("pageSize", feedService.pageSize()); .data("pageSize", feedService.pageSize())
.data("autoRefresh", false);
} }
@GET @GET
@@ -52,6 +53,7 @@ public class PageResource {
.data("nextOffset", rows.size()) .data("nextOffset", rows.size())
.data("hasMore", hasMore) .data("hasMore", hasMore)
.data("pageSize", feedService.pageSize()) .data("pageSize", feedService.pageSize())
.data("autoRefresh", false)
.data("followedCount", FollowedArtist.count()) .data("followedCount", FollowedArtist.count())
.data("feedCount", ReleaseGroupEntity.count()); .data("feedCount", ReleaseGroupEntity.count());
} }
@@ -2,6 +2,7 @@ package io.discdrop.service;
import io.discdrop.persistence.AppSetting; import io.discdrop.persistence.AppSetting;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@@ -22,6 +23,7 @@ public class SettingsService {
return parseTypes(raw); return parseTypes(raw);
} }
@Transactional
public void setDefaultPrimaryTypes(Set<String> types) { public void setDefaultPrimaryTypes(Set<String> types) {
AppSetting.set(KEY_DEFAULT_PRIMARY_TYPES, String.join(",", types)); AppSetting.set(KEY_DEFAULT_PRIMARY_TYPES, String.join(",", types));
} }
@@ -35,6 +37,7 @@ public class SettingsService {
} }
} }
@Transactional
public void setSyncScheduleHours(int hours) { public void setSyncScheduleHours(int hours) {
AppSetting.set(KEY_SYNC_SCHEDULE_HOURS, String.valueOf(hours)); AppSetting.set(KEY_SYNC_SCHEDULE_HOURS, String.valueOf(hours));
} }
@@ -0,0 +1,12 @@
/* DiscDrop custom styles */
html { scroll-behavior: smooth; }
/* Hide empty search dropdown */
#search-dropdown:empty { display: none; }
/* Remove native search input artifacts */
input[type="text"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
}

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 469 B

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 676 B

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

-3
View File
@@ -1,3 +0,0 @@
/* DiscDrop custom styles */
html { scroll-behavior: smooth; }
#search-dropdown:empty { display: none; }
+4 -4
View File
@@ -4,18 +4,18 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>DiscDrop Artists</title> <title>DiscDrop Artists</title>
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg"/> <link rel="icon" type="image/svg+xml" href="/img/favicon.svg"/>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css"/> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css"/>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/[email protected]"></script> <script src="https://unpkg.com/[email protected]"></script>
<link rel="stylesheet" href="/static/css/app.css"/> <link rel="stylesheet" href="/css/app.css"/>
<script src="/static/js/app.js" defer></script> <script src="/js/app.js" defer></script>
</head> </head>
<body> <body>
<header class="sticky top-0 z-30 bg-base-100/90 backdrop-blur border-b border-base-300"> <header class="sticky top-0 z-30 bg-base-100/90 backdrop-blur border-b border-base-300">
<div class="navbar flex flex-wrap justify-center gap-4 px-4 py-3 max-w-5xl mx-auto"> <div class="navbar flex flex-wrap justify-center gap-4 px-4 py-3 max-w-5xl mx-auto">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<img src="/static/img/logo.svg" alt="DiscDrop logo" class="h-9 w-9"/> <img src="/img/logo.svg" alt="DiscDrop logo" class="h-9 w-9"/>
<span class="text-xl font-bold">DiscDrop</span> <span class="text-xl font-bold">DiscDrop</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -16,3 +16,10 @@
<p class="text-sm">Search for an artist above and click <strong>Follow</strong> to start tracking their release groups.</p> <p class="text-sm">Search for an artist above and click <strong>Follow</strong> to start tracking their release groups.</p>
</div> </div>
{/if} {/if}
{#if autoRefresh?? && autoRefresh}
<div hx-get="/feed?offset=0"
hx-target="#feed-list"
hx-swap="innerHTML"
hx-trigger="load delay:3s"
class="hidden"></div>
{/if}
@@ -3,7 +3,7 @@
alt="cover" alt="cover"
loading="lazy" loading="lazy"
class="w-16 h-16 rounded object-cover bg-base-200" class="w-16 h-16 rounded object-cover bg-base-200"
onerror="this.onerror=null;this.src='/static/img/no-cover.svg'"/> onerror="this.onerror=null;this.src='/img/no-cover.svg'"/>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
+17 -18
View File
@@ -4,38 +4,37 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>DiscDrop</title> <title>DiscDrop</title>
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg"/> <link rel="icon" type="image/svg+xml" href="/img/favicon.svg"/>
<link rel="alternate" type="application/rss+xml" title="DiscDrop New Releases" href="/rss"/> <link rel="alternate" type="application/rss+xml" title="DiscDrop New Releases" href="/rss"/>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css"/> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css"/>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/[email protected]"></script> <script src="https://unpkg.com/[email protected]"></script>
<link rel="stylesheet" href="/static/css/app.css"/> <link rel="stylesheet" href="/css/app.css"/>
<script src="/static/js/app.js" defer></script> <script src="/js/app.js" defer></script>
</head> </head>
<body> <body>
<header class="sticky top-0 z-30 bg-base-100/90 backdrop-blur border-b border-base-300"> <header class="sticky top-0 z-30 bg-base-100/90 backdrop-blur border-b border-base-300">
<div class="navbar flex flex-wrap justify-center gap-4 px-4 py-3 max-w-5xl mx-auto"> <div class="navbar flex flex-wrap justify-center gap-4 px-4 py-3 max-w-5xl mx-auto">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<img src="/static/img/logo.svg" alt="DiscDrop logo" class="h-9 w-9"/> <img src="/img/logo.svg" alt="DiscDrop logo" class="h-9 w-9"/>
<span class="text-xl font-bold">DiscDrop</span> <span class="text-xl font-bold">DiscDrop</span>
</div> </div>
<div id="search-wrap" class="relative flex-1 min-w-[220px] max-w-xl"> <div id="search-wrap" class="relative flex-1 min-w-[220px] max-w-xl">
<form id="search-form" class="join w-full" autocomplete="off"> <input
<input id="search-input"
id="search-input" type="text"
type="search" name="q"
name="q" placeholder="Search MusicBrainz artists…"
placeholder="Search MusicBrainz artists…" class="input input-bordered w-full"
class="input input-bordered join-item w-full" autocomplete="off"
hx-get="/search/artists" hx-get="/search/artists"
hx-trigger="input changed delay:300ms" hx-trigger="input changed delay:300ms"
hx-target="#search-dropdown" hx-target="#search-dropdown"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-include="this"/> hx-include="this"/>
</form>
<div id="search-dropdown" <div id="search-dropdown"
class="absolute left-0 right-0 z-40 mt-1 bg-base-100 rounded-box shadow-lg border border-base-300 max-h-96 overflow-y-auto"></div> class="absolute left-0 right-0 top-full z-40 mt-1 bg-base-100 rounded-box shadow-lg border border-base-300 max-h-96 overflow-y-auto"></div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">