feat: add sorting and filtering in file list

This commit is contained in:
Louis Heredero 2025-05-03 11:18:40 +02:00
parent 51e01139ed
commit 7d3fac0847
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
5 changed files with 135 additions and 6 deletions

View File

@ -22,6 +22,31 @@
<div class="title"></div> <div class="title"></div>
<div class="episodes"><span class="num"></span> episode(s)</div> <div class="episodes"><span class="num"></span> episode(s)</div>
</a> </a>
<div class="toolbar">
<div class="tool">
<label for="sort-by">Sort by</label>
<select id="sort-by">
<option value="title">Title</option>
<option value="ts">Last modified</option>
</select>
</div>
<div class="tool">
<label for="sort-desc">Order</label>
<label class="toggle">
<input type="checkbox" id="sort-desc">
<div class="off">ASC</div>
<div class="on">DESC</div>
</label>
</div>
<div class="tool">
<label for="filter">Filter</label>
<select id="filter">
<option value="all">All</option>
<option value="film">Films</option>
<option value="series">Series</option>
</select>
</div>
</div>
<div id="files"></div> <div id="files"></div>
</main> </main>
</body> </body>

View File

@ -338,9 +338,11 @@ button.improve {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 30em; max-width: 30em;
transform: translateX(0%);
transition: transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
&:not(.show) { &:not(.show) {
display: none; transform: translateX(100%);
} }
#close-notifs { #close-notifs {

View File

@ -4,6 +4,7 @@
grid-auto-rows: 15em; grid-auto-rows: 15em;
gap: 0.8em; gap: 0.8em;
place-items: center; place-items: center;
padding: 0.8em 0;
.file { .file {
display: flex; display: flex;
@ -19,6 +20,10 @@
padding: 0.4em; padding: 0.4em;
border-radius: 1.2em; border-radius: 1.2em;
&.hidden {
display: none;
}
&:hover { &:hover {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
@ -35,3 +40,57 @@
} }
} }
} }
.toolbar {
display: flex;
gap: 1.2em;
border-bottom: solid black 1px;
padding: 0.4em 0;
.tool {
display: flex;
flex-direction: column;
gap: 0.2em;
label[for] {
font-weight: bold;
}
input, select {
font-family: inherit;
font-size: inherit;
height: 100%;
}
.toggle {
height: 2em;
border-radius: 1em;
display: grid;
grid-template-columns: 1fr 1fr;
user-select: none;
cursor: pointer;
input {
display: none;
&:not(:checked) ~ .off, &:checked ~ .on {
background-color: #6ee74a;
}
}
div {
padding: 0 0.4em;
display: grid;
place-items: center;
&.off {
border-radius: 1em 0 0 1em;
}
&.on {
border-radius: 0 1em 1em 0;
}
}
}
}
}

View File

@ -1,3 +1,5 @@
let fileNodes = []
function makeFilm(meta) { function makeFilm(meta) {
const file = document.getElementById("film-template").cloneNode(true) const file = document.getElementById("film-template").cloneNode(true)
file.querySelector(".title").innerText = meta.title file.querySelector(".title").innerText = meta.title
@ -47,13 +49,59 @@ function addFiles(files) {
const meta = files[i] const meta = files[i]
const file = makeFile(meta) const file = makeFile(meta)
list.appendChild(file) list.appendChild(file)
fileNodes.push([meta, file])
}) })
} }
function sortFiles() {
const sortBy = document.getElementById("sort-by").value
const sortDesc = document.getElementById("sort-desc").checked
const filter = document.getElementById("filter").value
fileNodes.forEach(([meta, node]) => {
if (node.classList.contains(filter) || filter === "all") {
node.classList.remove("hidden")
} else {
node.classList.add("hidden")
}
})
let changed = false
do {
changed = false
for (let i = 0; i < fileNodes.length - 1; i++) {
/** @type {[object, HTMLElement]} */
const pair1 = fileNodes[i]
/** @type {[object, HTMLElement]} */
const pair2 = fileNodes[i + 1]
const [meta1, node1] = pair1
const [meta2, node2] = pair2
let swap = false
if (sortDesc) {
swap = !(meta1[sortBy] >= meta2[sortBy])
} else {
swap = !(meta2[sortBy] >= meta1[sortBy])
}
if (swap) {
fileNodes[i] = pair2
fileNodes[i + 1] = pair1
node2.parentElement.insertBefore(node2, node1)
changed = true
}
}
} while (changed)
}
window.addEventListener("load", () => { window.addEventListener("load", () => {
fetch("/api/files").then(res => { fetch("/api/files").then(res => {
return res.json() return res.json()
}).then(files => { }).then(files => {
addFiles(files) addFiles(files)
sortFiles()
}) })
document.getElementById("sort-by").addEventListener("change", () => sortFiles())
document.getElementById("sort-desc").addEventListener("click", () => sortFiles())
document.getElementById("filter").addEventListener("change", () => sortFiles())
}) })

View File

@ -1,5 +0,0 @@
export default class MediaFile {
constructor(data) {
}
}