Item list management

This commit is contained in:
Tris Forster 2026-08-05 08:10:25 +10:00
parent 471e465133
commit ab123ba7b1
3 changed files with 153 additions and 83 deletions

View File

@ -2,8 +2,6 @@
{% load polyphonic %} {% load polyphonic %}
{% block page %} {% block page %}
<form action="" method="post" target="_blank">
{% csrf_token %}
<div> <div>
<div class="field is-grouped is-grouped-centered is-grouped-multiline"> <div class="field is-grouped is-grouped-centered is-grouped-multiline">
<div class="field has-addons control has-addons-centered is-expanded"> <div class="field has-addons control has-addons-centered is-expanded">
@ -16,7 +14,7 @@
<input type="text" class="input" list="instrument-list" id="instrument-name" onchange="updateParts()"/> <input type="text" class="input" list="instrument-list" id="instrument-name" onchange="updateParts()"/>
<datalist id="instrument-list"> <datalist id="instrument-list">
{% for inst in instruments %} {% for inst in instruments %}
<option value="{{inst.1}}"/> <option value="{{inst}}"/>
{% endfor %} {% endfor %}
</datalist> </datalist>
</span> </span>
@ -35,6 +33,9 @@
</div> </div>
</div> </div>
<form action="" method="post" target="_blank">
{% csrf_token %}
<table class="table is-striped mx-auto"> <table class="table is-striped mx-auto">
<thead> <thead>
<tr> <tr>
@ -58,7 +59,7 @@
{% endif %} {% endif %}
</td> </td>
<td class="is-hidden-mobile">{{ item.work.composer }}</td> <td class="is-hidden-mobile">{{ item.work.composer }}</td>
<td class="is-hidden-mobile">{% firstof item.work.running_time "------" %}</td> <td class="is-hidden-mobile">{% firstof item.work.running_time "-:--" %}</td>
<td class="select-cell"> <td class="select-cell">
<input type="hidden" name="works" value="{{ item.work.pk }}"/> <input type="hidden" name="works" value="{{ item.work.pk }}"/>
<span class="select is-small" style="width: 100%"> <span class="select is-small" style="width: 100%">
@ -84,7 +85,7 @@
<td/> <td/>
<td/> <td/>
<td/> <td/>
<td>{% firstof running_time "------" %}</td> <td>{% firstof running_time "-:--" %}</td>
<td colspan="2"> <td colspan="2">
<button class="button is-link is-small" type="submit" name="action" value="pdf"> <button class="button is-link is-small" type="submit" name="action" value="pdf">
{% icon "two_pager" %} {% icon "two_pager" %}
@ -114,50 +115,49 @@
const INSTRUMENTS = JSON.parse(document.getElementById('instruments').innerText); const INSTRUMENTS = JSON.parse(document.getElementById('instruments').innerText);
function updateParts() { function updateParts() {
var inst = document.getElementById("instrument-name").value.toLowerCase(); var inst = document.getElementById("instrument-name").value;
window.localStorage.setItem('instrument-name', inst); window.localStorage.setItem('instrument-name', inst);
console.log("Changing to", inst); console.log("Changing to", inst);
for (let i of INSTRUMENTS) { for (let i in INSTRUMENTS) {
if (i[1].toLowerCase() === inst) { if (i.toLowerCase() === inst.toLowerCase()) {
inst = i[0]; inst = i;
break; break;
} }
} }
console.log("Instrument code:", inst);
let part = document.getElementById("part-preference").value; let part = document.getElementById("part-preference").value;
window.localStorage.setItem('part-preference', part); window.localStorage.setItem('part-preference', part);
console.log("Part preference:", part); console.log("Part preference:", inst, part);
selectParts(inst, part); selectParts(INSTRUMENTS[inst] || [inst.toLowerCase()], part);
} }
function selectParts(inst, part) { function selectParts(codes, part) {
let n = parseInt(part);
let preferences = [];
for (let i=n; i>0; i--) {
for (let code of codes) preferences.push(code + "-" + i);
}
preferences.push(...codes);
console.log("Preferences:", preferences);
let prefix = inst + "-" + part;
let selections = document.getElementsByName("instrument-selection"); let selections = document.getElementsByName("instrument-selection");
console.log("Updating selections:", prefix, selections);
for(let i=0; i<selections.length; i++) { for(let i=0; i<selections.length; i++) {
var result = "-" const selection = selections[i];
let options = selections[i].children; for(let pref of preferences) {
for(let j=0; j<options.length; j++) { selection.value = pref;
let value = options[j].value; if(selection.value == pref) break;
if (value.startsWith(prefix)) {
result = value;
break;
}
if (result==="-" && value.startsWith(inst)) {
result = value;
}
} }
console.log("Selected:", result);
selections[i].value = result; if(!selection.value) selection.value = "-";
console.log("Selected:", selection.value);
} }
} }
@ -170,7 +170,7 @@ function downloadPart(collection, work) {
} }
console.log(part); console.log(part);
let url = "/collections/" + collection + "/works/" + work + "/download?tag=" + part; let url = "/collections/" + collection + "/works/" + work + "/download?tag=" + part;
window.location = url; window.open(url, "_blank");
} }
document.getElementById("instrument-name").value = localStorage.getItem('instrument-name', 'All'); document.getElementById("instrument-name").value = localStorage.getItem('instrument-name', 'All');

View File

@ -13,37 +13,57 @@
{% endblock %} {% endblock %}
{% block page %} {% block page %}
<table style="max-width: 600px; margin: 10pt auto;" class="zebra"> <div class="columns">
<thead>
<tr>
<th>Item</th>
<th>Time</th>
<th/>
</tr>
</thead>
<tbody id="work-list">
{% for item in object_list %}
<tr data-pk="{{ item.pk }}" data-order="{{ forloop.counter }}">
<td>{{ item.work.name }}</td>
<td>{{ item.work.duration }}</td>
<td style="text-align: center;">
<span class="clickable" title="Move up" onclick="moveItem({{ item.pk }}, -1)">
{% icon "arrow_upward" %}
</span>
<span class="clickable" title="Move down" onclick="moveItem({{ item.pk }}, 1)">
{% icon "arrow_downward" %}
</span>
<span class="clickable" title="Remove" onClick="moveItem({{ item.pk }}, 0)">
{% icon "delete" %}
</span>
</td>
</tr> <div class="column">
<h3 class="subtitle">Add from collection</h3>
<div>
{% for access in project.ensemble.allowed_collections.all %}
<a href="" class="button is-link">{{ access.collection.name }}</a>
{% endfor %}
</div>
</div>
<div class="column card">
<table style="max-width: 600px; margin: 10pt auto;" class="table is-striped is-narrow">
<thead>
<tr>
<th>Item</th>
<th>Time</th>
<th/>
</tr>
</thead>
<tbody id="work-list">
{% for item in object_list %}
<tr data-pk="{{ item.pk }}" data-order="{{ forloop.counter }}">
<td>{{ item.work.name }}</td>
<td style="min-width: 100px;">{% firstof item.work.duration '-:--' %}</td>
<td style="text-align: center;">
<span class="clickable cursor-pointer" title="Remove" onClick="removeItem({{ item.pk }})">
{% icon "delete" %}
</span>
<span class="clickable drag-handle cursor-drag" title="Reorder">
{% icon "reorder" %}
</span>
</td>
</tr>
{% empty %} {% empty %}
<tr><td colspan="2">No items</td></tr> <tr><td colspan="2">No items</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div>
</div>
{% endblock %}
{% block style %}
{{ block.super }}
<style>
tr.dragging { opacity: 0.4; }
.drag-handle { cursor: move; }
</style>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
@ -51,36 +71,81 @@
let workList = document.getElementById('work-list'); let workList = document.getElementById('work-list');
var dirty = false; var dirty = false;
var dragSrc = null;
var dragSrcNext = null;
function moveItem(pk, dir) { function reorderItems() {
let items = Array.prototype.slice.call(workList.children, 0);
for (let j = 0; j < items.length; j++) {
items[j].dataset.order = j;
}
}
function removeItem(pk) {
let items = Array.prototype.slice.call(workList.children, 0); let items = Array.prototype.slice.call(workList.children, 0);
var i=0;
pk = "" + pk; pk = "" + pk;
for(i=0; i<items.length; i++) { for (let i = 0; i < items.length; i++) {
if(items[i].dataset.pk === pk) break; if (items[i].dataset.pk === pk) {
} items[i].dataset.order = -1;
if(i >= items.length) return; items[i].style = "display: none";
break;
// check the direction is sensible }
if (i + dir < 0 || i + dir >= items.length) return;
if (dir === 0) {
items[i].dataset.order = -1;
items[i].style = "display: none";
} else {
items[i].dataset.order = parseInt(items[i].dataset.order) + dir;
items[i+dir].dataset.order = parseInt(items[i+dir].dataset.order) - dir;
}
items.sort((a, b) => parseInt(a.dataset.order) - parseInt(b.dataset.order))
workList.innerHTML = ""
for(let j=0; j<items.length; j++) {
workList.appendChild(items[j]);
} }
dirty = true; dirty = true;
} }
workList.addEventListener('mousedown', function(e) {
let handle = e.target.closest('.drag-handle');
if (handle) handle.closest('tr').draggable = true;
});
workList.addEventListener('mouseup', function(e) {
let handle = e.target.closest('.drag-handle');
if (handle) handle.closest('tr').draggable = false;
});
workList.addEventListener('dragstart', function(e) {
dragSrc = e.target.closest('tr');
if (!dragSrc) return;
dragSrcNext = dragSrc.nextElementSibling;
dragSrc.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', dragSrc.dataset.pk);
});
workList.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
let target = e.target.closest('tr');
if (!target || target === dragSrc) return;
let rect = target.getBoundingClientRect();
let midY = rect.top + rect.height / 2;
if (e.clientY < midY) {
workList.insertBefore(dragSrc, target);
} else {
workList.insertBefore(dragSrc, target.nextElementSibling);
}
});
workList.addEventListener('drop', function(e) {
e.preventDefault();
dragSrcNext = null;
reorderItems();
dirty = true;
});
workList.addEventListener('dragend', function(e) {
if (!dragSrc) return;
dragSrc.classList.remove('dragging');
if (dragSrcNext) {
workList.insertBefore(dragSrc, dragSrcNext);
}
dragSrc = null;
dragSrcNext = null;
});
function save() { function save() {
let items = Array.prototype.slice.call(workList.children, 0); let items = Array.prototype.slice.call(workList.children, 0);
@ -98,7 +163,7 @@ function save() {
}).then((response) => { }).then((response) => {
if (response.ok) { if (response.ok) {
dirty=false; dirty=false;
window.location = "{% url 'item_list' project=project.pk %}"; window.location = "{% url 'project_detail' project=project.pk %}";
} else { } else {
alert("Failed: " + response.statusText) alert("Failed: " + response.statusText)
} }

View File

@ -27,6 +27,11 @@ urlpatterns = [
), ),
path("library", views.LibraryWorkListView.as_view(), name="work_list"), path("library", views.LibraryWorkListView.as_view(), name="work_list"),
path("collections", views.CollectionListView.as_view(), name="collection_list"), path("collections", views.CollectionListView.as_view(), name="collection_list"),
path(
"projects/<int:project>/collections",
views.ProjectWorkListView.as_view(),
name="project_work_list",
),
path( path(
"collections/<int:collection>", "collections/<int:collection>",
views.CollectionWorkListView.as_view(), views.CollectionWorkListView.as_view(),