Skip to content

Commit da31bac

Browse files
Somewhat nicer import UI.
1 parent 8cfaeed commit da31bac

File tree

4 files changed

+127
-9
lines changed

4 files changed

+127
-9
lines changed

internal/admin/handlers.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"log"
77
"net/http"
8+
"strconv"
89
"strings"
910
"text/template"
1011
"time"
@@ -246,6 +247,125 @@ func (h *Handler) AlbumListHandler(c echo.Context) error {
246247
return c.HTML(200, sb.String())
247248
}
248249

250+
func (h *Handler) ImportStartHandler(c echo.Context) error {
251+
if err := validateAuth(c); err != nil {
252+
return err
253+
}
254+
urls := strings.Split(c.FormValue("bandcampUrls"), "\n")
255+
var albumRows strings.Builder
256+
var urlList []string
257+
for i, url := range urls {
258+
url = strings.TrimSpace(url)
259+
if url == "" {
260+
continue
261+
}
262+
urlList = append(urlList, url)
263+
albumRows.WriteString(`<div id="album-status-` + fmt.Sprint(i) + `" class="flex items-center gap-2 py-2 border-b border-gray-700">`)
264+
albumRows.WriteString(`<span class="w-8 h-8 flex items-center justify-center bg-gray-700 rounded-full text-gray-400">`)
265+
albumRows.WriteString(fmt.Sprint(i + 1))
266+
albumRows.WriteString(`</span>`)
267+
albumRows.WriteString(`<span class="flex-1 text-gray-200">` + template.HTMLEscapeString(url) + `</span>`)
268+
albumRows.WriteString(`<span class="text-yellow-400">Pending</span>`)
269+
albumRows.WriteString(`</div>`)
270+
}
271+
progressBar := `<div class="w-full bg-gray-700 rounded-full h-4 mb-4">
272+
<div id="import-progress-bar" class="bg-blue-600 h-4 rounded-full" style="width:0%"></div>
273+
</div>`
274+
html := `<div id="import-progress-container">` + progressBar + `<div id="import-album-status-list">` + albumRows.String() + `</div></div>`
275+
// Add a script to trigger the first import if there are any URLs
276+
if len(urlList) > 0 {
277+
html += `<div hx-post="/admin/import/process"
278+
hx-target="#album-status-0"
279+
hx-swap="outerHTML"
280+
hx-vals='{"albumIndex":0,"total":` + fmt.Sprint(len(urlList)) + `,"bandcampUrls":"` + template.JSEscapeString(strings.Join(urlList, "\\n")) + `"}'
281+
hx-trigger="load"></div>`
282+
// Add a script to reload the import tab after a delay (when all are done)
283+
html += `<div id="import-finish-trigger"></div>`
284+
html += `<script>
285+
function checkImportDone() {
286+
var lastRow = document.getElementById('album-status-` + fmt.Sprint(len(urlList)-1) + `');
287+
if (lastRow && lastRow.querySelector('.text-green-400, .text-red-400, .text-yellow-400')) {
288+
setTimeout(function() {
289+
htmx.ajax('GET', '/admin/content/import', {target: '#admin-content', swap: 'innerHTML'});
290+
document.getElementById('import-progress-global').innerHTML = '';
291+
setTimeout(function() {
292+
var msg = document.getElementById('import-status-message');
293+
if (msg) msg.innerHTML = '<div class="bg-green-700 text-white p-2 rounded mb-2">Import complete!</div>';
294+
}, 500);
295+
}, 1000);
296+
} else {
297+
setTimeout(checkImportDone, 500);
298+
}
299+
}
300+
checkImportDone();
301+
</script>`
302+
}
303+
return c.HTML(200, html)
304+
}
305+
306+
func (h *Handler) ImportProcessHandler(c echo.Context) error {
307+
if err := validateAuth(c); err != nil {
308+
return err
309+
}
310+
albumIndex, _ := strconv.Atoi(c.FormValue("albumIndex"))
311+
total, _ := strconv.Atoi(c.FormValue("total"))
312+
urls := strings.Split(c.FormValue("bandcampUrls"), "\n")
313+
if albumIndex >= len(urls) {
314+
return nil
315+
}
316+
url := strings.TrimSpace(urls[albumIndex])
317+
status := ""
318+
color := ""
319+
icon := ""
320+
if url == "" {
321+
status = "Skipped"
322+
color = "text-gray-400"
323+
icon = "-"
324+
} else if !strings.Contains(url, "bandcamp.com") {
325+
status = "Invalid URL"
326+
color = "text-red-400"
327+
icon = "✗"
328+
} else {
329+
exists, err := loader.AlbumUrlExists(url)
330+
if err != nil {
331+
status = "Error"
332+
color = "text-red-400"
333+
icon = "✗"
334+
} else if exists {
335+
status = "Already Imported"
336+
color = "text-yellow-400"
337+
icon = "!"
338+
} else {
339+
albumData, err := fetch.FetchFromBandcamp(url)
340+
if err != nil {
341+
status = "Fetch Error"
342+
color = "text-red-400"
343+
icon = "✗"
344+
} else if err := loader.SaveAlbum(albumData); err != nil {
345+
status = "Save Error"
346+
color = "text-red-400"
347+
icon = "✗"
348+
} else {
349+
status = "Imported"
350+
color = "text-green-400"
351+
icon = "✓"
352+
}
353+
}
354+
}
355+
row := `<div id="album-status-` + fmt.Sprint(albumIndex) + `" class="flex items-center gap-2 py-2 border-b border-gray-700" hx-swap-oob="true">`
356+
row += `<span class="w-8 h-8 flex items-center justify-center bg-gray-700 rounded-full ` + color + `">` + icon + `</span>`
357+
row += `<span class="flex-1 text-gray-200">` + template.HTMLEscapeString(url) + `</span>`
358+
row += `<span class="` + color + `">` + status + `</span>`
359+
row += `</div>`
360+
progress := int(float64(albumIndex+1) / float64(total) * 100)
361+
progressBar := `<div id="import-progress-bar" class="bg-blue-600 h-4 rounded-full" style="width:` + fmt.Sprint(progress) + `%;" hx-swap-oob="true"></div>`
362+
triggerNext := ""
363+
if albumIndex+1 < total {
364+
triggerNext = `<div hx-post=\"/admin/import/process\" hx-target=\"#album-status-` + fmt.Sprint(albumIndex+1) + `\" hx-swap=\"outerHTML\" hx-vals='{"albumIndex":` + fmt.Sprint(albumIndex+1) + `,"total":` + fmt.Sprint(total) + `,"bandcampUrls":"` + template.JSEscapeString(strings.Join(urls, "\\n")) + `"}' hx-trigger=\"load\"></div>`
365+
}
366+
return c.HTML(200, row+progressBar+triggerNext)
367+
}
368+
249369
func validateAuth(c echo.Context) error {
250370
cookie, err := c.Cookie("session")
251371
if err != nil {

internal/admin/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ func SetupRoutes(e *echo.Echo, h *Handler) {
1010
admin.POST("/auth", h.AdminAuthHandler)
1111
admin.GET("/content/import", h.AdminImportHandler)
1212
admin.POST("/import", h.ImportAlbumHandler)
13+
admin.POST("/import/start", h.ImportStartHandler)
14+
admin.POST("/import/process", h.ImportProcessHandler)
1315
admin.POST("/fetch/metal-archives", h.FetchMetalArchivesHandler)
1416
admin.POST("/validate/metal-archives-url", h.ValidateMetalArchivesUrlHandler)
1517
admin.GET("/logout", h.LogoutHandler)

templates/admin/components/import-form.html

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
{{ define "admin/components/import-form.html" }}
2-
<div class="bg-gray-800 p-4 rounded-lg">
3-
<div id="import-status" class="mt-4 space-y-2 bg-gray-900 p-4 rounded max-h-64 overflow-y-auto"></div>
4-
5-
{{ template "loading" "Importing albums..." }}
6-
7-
<form hx-post="/admin/import"
8-
hx-target="#import-status"
9-
hx-indicator="#loading-indicator">
2+
<div class="bg-gray-800 p-4 rounded-lg">
3+
<div id="import-status-message"></div>
4+
<form id="import-form" hx-post="/admin/import/start" hx-target="#import-progress-global" hx-swap="innerHTML">
105
<div class="space-y-4">
116
<div>
127
<label class="block text-sm font-medium mb-2">Bandcamp URLs (one per line)</label>
13-
<textarea name="bandcampUrls" rows="5"
8+
<textarea name="bandcampUrls" rows="5"
149
class="w-full p-2 bg-gray-700 text-gray-200 rounded border border-gray-600 focus:border-blue-500"
1510
placeholder="https://artist1.bandcamp.com/album/name1&#10;https://artist2.bandcamp.com/album/name2"
1611
required></textarea>

templates/admin/layout/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<div class="bg-gray-800 rounded-lg shadow-lg p-6">
99
{{ block "content" . }}{{ end }}
1010
</div>
11+
<div id="import-progress-global" class="mt-6"></div>
1112
{{ else }}
1213
{{ template "admin/pages/login.html" . }}
1314
{{ end }}

0 commit comments

Comments
 (0)