Switch to using newtable partial for all tables

Will be renamed eventually, don't care right now. Also cleans up a lot of code I wasn't particularly happy about
This commit is contained in:
mitteneer 2025-05-13 14:24:14 -04:00
parent 153ea869e0
commit 365b9dbf11
10 changed files with 128 additions and 166 deletions

View file

@ -17,8 +17,8 @@
// internet connectivity. // internet connectivity.
.dependencies = .{ .dependencies = .{
.jetzig = .{ .jetzig = .{
.url = "https://github.com/jetzig-framework/jetzig/archive/a298192bb0cddf9c45d7d0d976a9852804457de2.tar.gz", .url = "https://github.com/jetzig-framework/jetzig/archive/7be1d137fcab5c422e05f12092f6e04a02900d6f.tar.gz",
.hash = "jetzig-0.0.0-IpAgLf5aDwB4UOKMhIjtK22zBsPfbWEwCYgHT0hayyT-", .hash = "jetzig-0.0.0-IpAgLURbDwB1NlywUH7lnQ3zptNvSQWVosaA1k7l1cNz",
}, },
.zeit = .{ .zeit = .{
.url = "https://github.com/rockorager/zeit/archive/refs/tags/v0.6.0.tar.gz", .url = "https://github.com/rockorager/zeit/archive/refs/tags/v0.6.0.tar.gz",

View file

@ -1,6 +1,8 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("jetzig"); const jetzig = @import("jetzig");
const jetquery = @import("jetzig").jetquery; const jetquery = @import("jetzig").jetquery;
const TableRow = @import("../../types.zig").TableRows;
const HyperlinkData = @import("../../types.zig").HyperlinkData;
pub fn index(request: *jetzig.Request) !jetzig.View { pub fn index(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object); var root = try request.data(.object);
@ -22,28 +24,26 @@ pub fn index(request: *jetzig.Request) !jetzig.View {
const Album = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 }; const Album = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 };
var prev_album_id: ?i32 = null; var prev_album_id: ?i32 = null;
var prev_artist_infos: ?*jetzig.zmpl.Data.Value = null;
var row: ?TableRow = null;
var artistlist = std.ArrayList(HyperlinkData).init(request.allocator);
blk: while (try albums_jq_result.postgresql.result.next()) |album_row| { blk: while (try albums_jq_result.postgresql.result.next()) |album_row| {
const album = try album_row.to(Album, .{ .dupe = true, .allocator = request.allocator }); const album = try album_row.to(Album, .{ .dupe = true, .allocator = request.allocator });
if (album.id == prev_album_id) { if (album.id == prev_album_id) {
var artist_info = try prev_artist_infos.?.append(.object); try artistlist.append(.{ .name = album.artist_name, .id = album.artist_id });
try artist_info.put("name", album.artist_name);
try artist_info.put("id", album.artist_id);
continue :blk; continue :blk;
} else {
try artistlist.append(.{ .name = album.artist_name, .id = album.artist_id });
row = TableRow{
.album = .{ .name = album.name, .id = album.id },
.artistlist = try artistlist.toOwnedSlice(),
.scrobbles = album.scrobbles,
};
try albums_view.append(row);
} }
var album_view = try albums_view.append(.object);
try album_view.put("name", album.name);
try album_view.put("id", album.id);
try album_view.put("scrobbles", album.scrobbles);
var artist_infos = try album_view.put("artist_info", .array);
var artist_info = try artist_infos.append(.object);
try artist_info.put("name", album.artist_name);
try artist_info.put("id", album.artist_id);
prev_artist_infos = artist_infos;
prev_album_id = album.id; prev_album_id = album.id;
} }
return request.render(.ok); return request.render(.ok);
@ -72,10 +72,12 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
while (try songs_js_result.postgresql.result.next()) |song_row| { while (try songs_js_result.postgresql.result.next()) |song_row| {
const song = try song_row.to(Song, .{ .dupe = true, .allocator = request.allocator }); const song = try song_row.to(Song, .{ .dupe = true, .allocator = request.allocator });
var song_view = try songs_view.append(.object); const row = TableRow{
try song_view.put("name", song.name); .song = .{ .name = song.name, .id = song.id },
try song_view.put("id", song.id); .scrobbles = song.scrobbles,
try song_view.put("scrobbles", song.scrobbles); };
try songs_view.append(row);
} }
return request.render(.ok); return request.render(.ok);
} }

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("jetzig"); const jetzig = @import("jetzig");
const jetquery = @import("jetzig").jetquery; const jetquery = @import("jetzig").jetquery;
const TableRow = @import("../../types.zig").TableRows;
const dateFmt = @import("../../date_fmt.zig").dateFmt; const dateFmt = @import("../../date_fmt.zig").dateFmt;
const ordinalFmt = @import("../../ordinal_fmt.zig").ordinalFmt; const ordinalFmt = @import("../../ordinal_fmt.zig").ordinalFmt;
@ -25,10 +26,12 @@ pub fn index(request: *jetzig.Request) !jetzig.View {
while (try artists_jq_result.postgresql.result.next()) |artist_row| { while (try artists_jq_result.postgresql.result.next()) |artist_row| {
const artist = try artist_row.to(Artist, .{ .dupe = true, .allocator = request.allocator }); const artist = try artist_row.to(Artist, .{ .dupe = true, .allocator = request.allocator });
var artist_view = try artists_view.append(.object); const row = TableRow{
try artist_view.put("name", artist.name); .artist = .{ .name = artist.name, .id = artist.id },
try artist_view.put("url", artist.id); .scrobbles = artist.scrobbles,
try artist_view.put("scrobbles", artist.scrobbles); };
try artists_view.append(row);
} }
return request.render(.ok); return request.render(.ok);
@ -84,7 +87,12 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
while (try albums_jq_result.postgresql.result.next()) |album_row| { while (try albums_jq_result.postgresql.result.next()) |album_row| {
const album = try album_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator }); const album = try album_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator });
try albums_view.append(album); const album_table_row = TableRow{
.album = .{ .name = album.name, .id = album.id },
.scrobbles = album.scrobbles,
};
try albums_view.append(album_table_row);
} }
//albums_jq_result.drain(); //albums_jq_result.drain();
@ -107,7 +115,12 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
while (try appears_jq_result.postgresql.result.next()) |appears_row| { while (try appears_jq_result.postgresql.result.next()) |appears_row| {
const appears = try appears_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator }); const appears = try appears_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator });
try appears_view.append(appears); const appears_table_row = TableRow{
.album = .{ .name = appears.name, .id = appears.id },
.scrobbles = appears.scrobbles,
};
try appears_view.append(appears_table_row);
} }
//appears_jq_result.drain(); //appears_jq_result.drain();

View file

@ -1,34 +1,15 @@
@zig {
const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date};
const columns: ColumnChoices = &.{.artist, .scrobbles};
}
<html> <html>
<head> <head>
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<meta charset="UTF-8"> <meta charset="UTF-8">
</head> </head>
<body> <body>
@partial partials/header @partial partials/header
<h1>Artists</h1> <h1>Artists</h1>
<table id="myTable" class='table'> @partial partials/newtable(T: ColumnChoices, table_data: .artists, columns: columns)
<thead>
<tr>
<th>Name</th>
<th>Scrobbles</th>
</tr>
</thead>
<tbody>
@for (.artists) |artist| {
<tr>
<td class=cell><a href="/artists/{{artist.url}}">{{artist.name}}</a></td>
<td class=cell>{{artist.scrobbles}}</td>
</tr>
}
</tbody>
</table>
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" type="text/javascript"></script>
<script>
const dataTable = new simpleDatatables.DataTable("#myTable", {
searchable: true,
perPage: 50,
perPageSelect: [25,50,100],
});
</script>
</body> </body>
</html> </html>

View file

@ -32,33 +32,37 @@
<tbody> <tbody>
@zig { @zig {
const array = table_data.items(.array); const array = table_data.items(.array);
for (array) |ent| { for (array) |row| {
<tr> <tr>
for (columns) |header| { for (columns) |header| {
switch (header) { switch (header) {
.song, .album, .artist => { .song => {
const path = switch (header) {
.song => "songs",
.album => "albums",
.artist => "artists",
else => unreachable
};
<td class=cell> <td class=cell>
<a href="/{{path}}/{{ent.id}}">{{ent.name}}</a> <a href="/songs/{{row.song.id}}">{{row.song.name}}</a>
</td>
},
.album => {
<td class=cell>
<a href="/albums/{{row.album.id}}">{{row.album.name}}</a>
</td>
},
.artist => {
<td class=cell>
<a href="/artists/{{row.artist.id}}">{{row.artist.name}}</a>
</td> </td>
}, },
.artistlist => { .artistlist => {
<td class=cell> <td class=cell>
@for (ent.get("artist_info").?) |artist| { @for (row.get("artistlist").?) |artist| {
<a href="/artists/{{artist.id}}">{{artist.name}}</a> <a href="/artists/{{artist.id}}">{{artist.name}}</a>
} }
</td> </td>
}, },
.scrobbles => { .scrobbles => {
<td class=cell>{{ent.scrobbles}}</td> <td class=cell>{{row.scrobbles}}</td>
}, },
.date =>{ .date =>{
<td class=cell>{{ent.date}}</td> <td class=cell>{{row.date}}</td>
} }
} }
} }

View file

@ -1,6 +1,8 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("jetzig"); const jetzig = @import("jetzig");
const zeit = @import("zeit"); const zeit = @import("zeit");
const TableRow = @import("../../types.zig").TableRows;
const HyperlinkData = @import("../../types.zig").HyperlinkData;
pub fn index(request: *jetzig.Request) !jetzig.View { pub fn index(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object); var root = try request.data(.object);
@ -23,33 +25,30 @@ pub fn index(request: *jetzig.Request) !jetzig.View {
const Scrobble = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, artist_name: []const u8, artist_id: i32, s_id: i32, date: i64 }; const Scrobble = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, artist_name: []const u8, artist_id: i32, s_id: i32, date: i64 };
var prev_s_id: ?i32 = null; var prev_s_id: ?i32 = null;
var prev_artist_infos: ?*jetzig.zmpl.Data.Value = null;
var row: ?TableRow = null;
var artistlist = std.ArrayList(HyperlinkData).init(request.allocator);
blk: while (try scrobbles_jq_result.postgresql.result.next()) |scrobble_row| { blk: while (try scrobbles_jq_result.postgresql.result.next()) |scrobble_row| {
const scrobble = try scrobble_row.to(Scrobble, .{ .dupe = true, .allocator = request.allocator }); const scrobble = try scrobble_row.to(Scrobble, .{ .dupe = true, .allocator = request.allocator });
if (scrobble.s_id == prev_s_id) { if (scrobble.s_id == prev_s_id) {
var artist_info = try prev_artist_infos.?.append(.object); try artistlist.append(.{ .name = scrobble.artist_name, .id = scrobble.artist_id });
try artist_info.put("name", scrobble.artist_name);
try artist_info.put("url", scrobble.artist_id);
continue :blk; continue :blk;
} else {
var date = std.ArrayList(u8).init(request.allocator);
try (try zeit.instant(.{ .source = .{ .unix_timestamp = @divFloor(scrobble.date, 1_000) } })).time().strftime(date.writer(), "%d %b %Y, %H:%M");
try artistlist.append(.{ .name = scrobble.artist_name, .id = scrobble.artist_id });
row = TableRow{
.song = .{ .name = scrobble.song_name, .id = scrobble.song_id },
.album = .{ .name = scrobble.album_name, .id = scrobble.album_id },
.artistlist = try artistlist.toOwnedSlice(),
.date = date.items,
};
try scrobbles_view.append(row);
} }
// Not appending the scrobble directly because we don't want the unix timestamp or scrobble id
var scrobble_view = try scrobbles_view.append(.object);
var artist_infos = try scrobble_view.put("artist_info", .array);
var artist_info = try artist_infos.append(.object);
try artist_info.put("name", scrobble.artist_name);
try artist_info.put("url", scrobble.artist_id);
try scrobble_view.put("song_name", scrobble.song_name);
try scrobble_view.put("song_id", scrobble.song_id);
try scrobble_view.put("album_name", scrobble.album_name);
try scrobble_view.put("album_id", scrobble.album_id);
var date = std.ArrayList(u8).init(request.allocator);
try (try zeit.instant(.{ .source = .{ .unix_timestamp = @divFloor(scrobble.date, 1_000) } })).time().strftime(date.writer(), "%d %b %Y, %H:%M");
try scrobble_view.put("date", date.items);
prev_artist_infos = artist_infos;
prev_s_id = scrobble.s_id; prev_s_id = scrobble.s_id;
} }

View file

@ -1,42 +1,15 @@
@zig {
const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date};
const columns: ColumnChoices = &.{.song, .artistlist, .album, .date};
}
<html> <html>
<head> <head>
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<meta charset="UTF-8"> <meta charset="UTF-8">
</head> </head>
<body> <body>
@partial partials/header @partial partials/header
<h1>Scrobbles</h1> <h1>Scrobbles</h1>
<table id="myTable"> @partial partials/newtable(T: ColumnChoices, table_data: .scrobbles, columns: columns)
<thead>
<tr>
<th>Song</th>
<th>Artist(s)</th>
<th>Album</th>
<th data-type="date" data-format="DD MMM YYYY, HH:mm">Date</th>
</tr>
</thead>
<tbody>
@for (.scrobbles) |scrobble| {
<tr>
<td class=cell><a href="/songs/{{scrobble.song_id}}">{{scrobble.song_name}}</a></td>
<td class=cell>
@for (scrobble.get("artist_info").?) |ai| {
<a href="/artists/{{ai.url}}">{{ai.name}}</a>
}
</td>
<td class=cell><a href="/albums/{{scrobble.album_id}}">{{scrobble.album_name}}</a></td>
<td class=cell>{{scrobble.date}}</td>
</tr>
}
</tbody>
</table>
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" type="text/javascript"></script>
<script>
const dataTable = new simpleDatatables.DataTable("#myTable", {
searchable: true,
perPage: 50,
perPageSelect: [25,50,100],
});
</script>
</body> </body>
</html> </html>

View file

@ -1,5 +1,7 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("jetzig"); const jetzig = @import("jetzig");
const TableRow = @import("../../types.zig").TableRows;
const HyperlinkData = @import("../../types.zig").HyperlinkData;
pub fn index(request: *jetzig.Request) !jetzig.View { pub fn index(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object); var root = try request.data(.object);
@ -22,28 +24,26 @@ pub fn index(request: *jetzig.Request) !jetzig.View {
const Song = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 }; const Song = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 };
var prev_song_id: ?i32 = null; var prev_song_id: ?i32 = null;
var prev_artist_infos: ?*jetzig.zmpl.Data.Value = null;
var row: ?TableRow = null;
var artistlist = std.ArrayList(HyperlinkData).init(request.allocator);
blk: while (try songs_js_result.postgresql.result.next()) |song_row| { blk: while (try songs_js_result.postgresql.result.next()) |song_row| {
const song = try song_row.to(Song, .{ .dupe = true, .allocator = request.allocator }); const song = try song_row.to(Song, .{ .dupe = true, .allocator = request.allocator });
if (song.id == prev_song_id) { if (song.id == prev_song_id) {
var artist_info = try prev_artist_infos.?.append(.object); try artistlist.append(.{ .name = song.artist_name, .id = song.artist_id });
try artist_info.put("name", song.artist_name);
try artist_info.put("url", song.artist_id);
continue :blk; continue :blk;
} else {
try artistlist.append(.{ .name = song.artist_name, .id = song.artist_id });
row = TableRow{
.song = .{ .name = song.name, .id = song.id },
.artistlist = try artistlist.toOwnedSlice(),
.scrobbles = song.scrobbles,
};
try songs_view.append(row);
} }
var song_view = try songs_view.append(.object);
var artist_infos = try song_view.put("artist_info", .array);
var artist_info = try artist_infos.append(.object);
try artist_info.put("name", song.artist_name);
try artist_info.put("url", song.artist_id);
try song_view.put("name", song.name);
try song_view.put("url", song.id);
try song_view.put("scrobbles", song.scrobbles);
prev_artist_infos = artist_infos;
prev_song_id = song.id; prev_song_id = song.id;
} }
return request.render(.ok); return request.render(.ok);

View file

@ -1,40 +1,15 @@
@zig {
const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date};
const columns: ColumnChoices = &.{.song, .artistlist, .scrobbles};
}
<html> <html>
<head> <head>
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<meta charset="UTF-8"> <meta charset="UTF-8">
</head> </head>
<body> <body>
@partial partials/header @partial partials/header
<h1>Songs</h1> <h1>Songs</h1>
<table id="myTable"> @partial partials/newtable(T: ColumnChoices, table_data: .songs, columns: columns)
<thead>
<tr>
<th>Name</th>
<th>Artist(s)</th>
<th>Scrobbles</th>
</tr>
</thead>
<tbody>
@for (.songs) |song| {
<tr>
<td class=cell><a href="/songs/{{song.url}}">{{song.name}}</a></td>
<td class=cell>
@for (song.get("artist_info").?) |ai| {
<a href="/artists/{{ai.url}}">{{ai.name}}</a>
}
</td>
<td class=cell>{{song.scrobbles}}</td>
</tr>
}
</tbody>
</table>
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" type="text/javascript"></script>
<script>
const dataTable = new simpleDatatables.DataTable("#myTable", {
searchable: true,
perPage: 50,
perPageSelect: [25,50,100],
});
</script>
</body> </body>
</html> </html>

View file

@ -72,3 +72,18 @@ pub const Rules = struct {
// scrobbles, // scrobbles,
// date, // date,
//}; //};
//
pub const TableRows = struct {
song: ?HyperlinkData = null,
album: ?HyperlinkData = null,
artist: ?HyperlinkData = null,
artistlist: ?[]HyperlinkData = null,
scrobbles: ?i64 = null,
date: ?[]const u8 = null,
};
pub const HyperlinkData = struct {
name: []const u8,
id: i32,
};