initial UI, pagination starts at 1 for some reason
This commit is contained in:
parent
474ea9b57c
commit
832e2025a0
@ -62,13 +62,14 @@ func (c *DiscogsCache) GetAllRecords(ctx context.Context) ([]media.Record, error
|
||||
func (c *DiscogsCache) fetchRecords(ctx context.Context, pagination *discogs.Pagination) ([]media.Record, error) {
|
||||
records := []media.Record{}
|
||||
if pagination == nil {
|
||||
pagination = getPagination(0)
|
||||
pagination = getPagination(1)
|
||||
}
|
||||
log.Printf("calling discogs API, page %v", pagination.Page)
|
||||
coll, err := c.client.CollectionItemsByFolder(c.username, 0, pagination)
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("error loading collection: %w", err)
|
||||
}
|
||||
log.Printf("length: %v, first item in list: %s", len(coll.Items), coll.Items[0].BasicInformation.Title)
|
||||
for i := range coll.Items {
|
||||
records = append(records, collectionItemToRecord(&coll.Items[i]))
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
<div class="wrapper">
|
||||
<div id="header">
|
||||
<h1>Library</h1>
|
||||
<a href="/records">records</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
168
frontend/files/records/app.js
Normal file
168
frontend/files/records/app.js
Normal file
@ -0,0 +1,168 @@
|
||||
var sortState = {
|
||||
sortBy: "sortArtist",
|
||||
sortOrder: "asc",
|
||||
};
|
||||
|
||||
function init() {
|
||||
fetch("/api/records")
|
||||
.then((response) => response.json())
|
||||
.then((records) => {
|
||||
// prepare response
|
||||
records.forEach(apiResponseParsing);
|
||||
document.getElementById("search").addEventListener("input", (e) => {
|
||||
renderTable(search(records, e.target.value));
|
||||
});
|
||||
renderTable(records);
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(records, sortField) {
|
||||
if (sortField) {
|
||||
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
|
||||
sortState.sortOrder = "desc";
|
||||
} else {
|
||||
sortState.sortOrder = "asc";
|
||||
}
|
||||
sortState.sortBy = sortField;
|
||||
}
|
||||
records.sort((one, two) =>
|
||||
(one[sortState.sortBy] + one["sortName"]).localeCompare(
|
||||
two[sortState.sortBy] + two["sortName"]
|
||||
)
|
||||
);
|
||||
if (sortState.sortOrder === "desc") {
|
||||
records.reverse();
|
||||
}
|
||||
records.forEach((e, i) => (e.rowNumber = i)); // re-key
|
||||
|
||||
// rendering
|
||||
var recordElement = document.getElementById("records");
|
||||
recordElement.innerHTML = TableTemplate(records);
|
||||
|
||||
// add listeners for selecting record to view
|
||||
Array.from(recordElement.querySelectorAll("tbody tr"))
|
||||
.slice(1) // remove header from Array
|
||||
.forEach((row) => {
|
||||
row.addEventListener("click", (e) => {
|
||||
// add listener to swap current record
|
||||
document.getElementById("current").innerHTML = RecordTemplate(
|
||||
records[e.currentTarget.id]
|
||||
);
|
||||
});
|
||||
});
|
||||
// add sorting callbacks
|
||||
Array.from(
|
||||
recordElement.querySelectorAll("tbody tr th[data-sort-by]")
|
||||
).forEach((row) => {
|
||||
row.addEventListener("click", function (e) {
|
||||
renderTable(records, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute
|
||||
});
|
||||
});
|
||||
// mark currently active column
|
||||
recordElement
|
||||
.querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]")
|
||||
.classList.add(sortState.sortOrder);
|
||||
}
|
||||
|
||||
function apiResponseParsing(record) {
|
||||
record.sortName = titleCleaner(record.name);
|
||||
return record;
|
||||
}
|
||||
|
||||
function search(records, searchBy) {
|
||||
searchBy = searchCleaner(searchBy);
|
||||
if (searchBy !== "") {
|
||||
records = records.filter(({ name, artists, genre, label, year }) => {
|
||||
return Object.values({
|
||||
name,
|
||||
artists: artists.join(" "),
|
||||
genre,
|
||||
label,
|
||||
year,
|
||||
}).find((field) => searchCleaner(field).indexOf(searchBy) !== -1);
|
||||
});
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
function titleCleaner(title) {
|
||||
return title
|
||||
.replace('"', "")
|
||||
.replace(":", "")
|
||||
.replace(/^(An?|The)\s/i, "");
|
||||
}
|
||||
|
||||
function searchCleaner(str) {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replaceAll('"', "")
|
||||
.replaceAll(":", "")
|
||||
.replaceAll("'", "")
|
||||
.replaceAll(" ", "");
|
||||
}
|
||||
|
||||
// AlbumName string `json:"name"`
|
||||
// Artists []string `json:"artists"`
|
||||
// SortArtist string `json:"sortArtist"`
|
||||
// Identifier string `json:"identifier"`
|
||||
// Format string `json:"format"`
|
||||
// Genre string `json:"genre"`
|
||||
// Label string `json:"label"`
|
||||
// Year string `json:"year"`
|
||||
// Description string `json:"description"`
|
||||
// CoverURL string `json:"coverURL"`
|
||||
// DiscogsURL string `json:"discogsURL"`
|
||||
|
||||
function RecordTemplate({
|
||||
name,
|
||||
artists,
|
||||
coverURL,
|
||||
description,
|
||||
format,
|
||||
genre,
|
||||
identifier,
|
||||
label,
|
||||
year,
|
||||
}) {
|
||||
return `${coverURL ? `<img src="${coverURL}"/>` : ""}
|
||||
<h1>${name}</h1>
|
||||
<h2>${artists.join(", ")}</h2>
|
||||
<span>${identifier}</span><br/>
|
||||
<span>${genre}, ${label}, ${year}</span><br/>
|
||||
<span>${format}</span>
|
||||
<div class="description">
|
||||
<p>${description}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function TableRowTemplate({
|
||||
artists,
|
||||
identifier,
|
||||
label,
|
||||
rowNumber,
|
||||
name,
|
||||
year,
|
||||
}) {
|
||||
return `<tr class="tRow" id="${rowNumber}">
|
||||
<td class="name">
|
||||
${name}
|
||||
</td>
|
||||
<td class="artist">${artists.join(", ")}</td>
|
||||
<td class="label">${label}</td>
|
||||
<td class="identifier">${identifier}</td>
|
||||
<td class="year">${year}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
function TableTemplate(records) {
|
||||
return `<table class="recordTable">
|
||||
<tr>
|
||||
<th data-sort-by="sortName" class="tHeader name">Name</th>
|
||||
<th data-sort-by="sortArtist" class="tHeader artist">Artist(s)</th>
|
||||
<th data-sort-by="label" class="tHeader label">Label</th>
|
||||
<th data-sort-by="identifier" class="tHeader identifier">Identifier</th>
|
||||
<th data-sort-by="year" class="tHeader year">Year</th>
|
||||
</tr>${records.reduce((acc, record) => {
|
||||
return acc.concat(TableRowTemplate(record));
|
||||
}, "")} </table>`;
|
||||
}
|
BIN
frontend/files/records/favicon.ico
Normal file
BIN
frontend/files/records/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
frontend/files/records/favicon.png
Normal file
BIN
frontend/files/records/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
46
frontend/files/records/index.html
Normal file
46
frontend/files/records/index.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Library</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,700&display=swap"
|
||||
as="style"
|
||||
rel="stylesheet preload prefetch"
|
||||
/>
|
||||
<script type="text/javascript" src="app.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div id="header">
|
||||
<h1>Records</h1>
|
||||
<a href="/">books</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://git.yetaga.in/alazyreader/library"
|
||||
>git</a
|
||||
>
|
||||
<div id="searchBox">
|
||||
<input
|
||||
id="search"
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="current">No Record Selected</div>
|
||||
<div id="records"></div>
|
||||
<!-- Table goes here -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
284
frontend/files/records/style.css
Normal file
284
frontend/files/records/style.css
Normal file
@ -0,0 +1,284 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: "";
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* site CSS starts here */
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 30px;
|
||||
width: calc(100vw - 20px);
|
||||
padding: 4px 10px;
|
||||
background-color: #f7f3dc;
|
||||
border-bottom: 2px solid #d8d0a0;
|
||||
font-family: "Libre Baskerville", sans-serif;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
font-size: xx-large;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 7px;
|
||||
text-align: right;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#searchBox input {
|
||||
width: 300px;
|
||||
font-size: 16px;
|
||||
background: #f9f8ed;
|
||||
padding: 2px 5px;
|
||||
border: none;
|
||||
border-bottom: 2px solid #d8d0a0;
|
||||
font-family: "Libre Baskerville", sans-serif;
|
||||
}
|
||||
|
||||
#searchBox input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#searchBox input::placeholder {
|
||||
font-family: "Libre Baskerville", sans-serif;
|
||||
color: #d8d0a0;
|
||||
}
|
||||
|
||||
#current {
|
||||
background-color: #f7f3dc;
|
||||
width: calc(40vw - 40px);
|
||||
height: calc(100vh - 80px);
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#records {
|
||||
width: calc(60vw - 40px);
|
||||
height: calc(100vh - 80px);
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.recordTable th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
font-family: "Libre Baskerville", sans-serif;
|
||||
}
|
||||
|
||||
.recordTable th[data-sort-by] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recordTable th[data-sort-by]::after {
|
||||
content: "\f0dc";
|
||||
font-family: FontAwesome;
|
||||
font-size: x-small;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.recordTable th.asc::after {
|
||||
content: "\f0de";
|
||||
font-family: FontAwesome;
|
||||
font-size: x-small;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.recordTable th.desc::after {
|
||||
content: "\f0dd";
|
||||
font-family: FontAwesome;
|
||||
font-size: x-small;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.recordTable td,
|
||||
.recordTable th {
|
||||
padding: 5px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.tRow:nth-child(odd) {
|
||||
background: #f9f8ed;
|
||||
border-bottom: 1px solid #d8d0a0;
|
||||
}
|
||||
|
||||
.recordTable .tRow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recordTable .onLoan {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.recordTable .tRow .title {
|
||||
font-style: italic;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
#current h1 {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#current h2 {
|
||||
font-size: large;
|
||||
padding: 7px 0;
|
||||
}
|
||||
|
||||
#current img {
|
||||
max-height: 400px;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#current .description p {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
#current h1.onLoan {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
#current h2.onLoan {
|
||||
font-weight: bold;
|
||||
}
|
Loading…
Reference in New Issue
Block a user