Refactored into /app
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
|
||||
{% block page %}
|
||||
<h3 class="title">Library collections for {{ request.user }}</h3>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
{% for collection in object_list %}
|
||||
<div class="column is-half">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<a class="" href="{% url 'collection_work_list' pk=collection.id %}">
|
||||
<p class="card-header-title">{{ collection.name }}</p>
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<p>{{ collection.location }}, {{ collection.works.count }} items.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<small>{{ ensemble.ensemble_code }}</small>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,389 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="#" onclick="saveTags()" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-save"></i></span>
|
||||
<span>Save</span>
|
||||
</a>
|
||||
<a href="{% url 'work_detail' pk=object.work.pk %}" class="button is-link is-light">
|
||||
<span>Cancel</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block media %}
|
||||
<style>
|
||||
.tag-grid {
|
||||
display: flex;
|
||||
}
|
||||
.grid-column {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.grid-page {
|
||||
height: 25px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.grid-page.is-active {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.grid-tag {
|
||||
border: 2px solid var(--primary);
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
border-radius: 5px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
margin-bottom: 5px;
|
||||
min-width: 200px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
#tag-area {
|
||||
min-width: 220px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<h3 class="subtitle"><a href="{% url 'work_detail' document.work.pk %}">{{ document.work.name }}</a></h3>
|
||||
<div id="annotation-area" class="columns is-centered">
|
||||
<div class="column is-narrow">
|
||||
<div class="has-text-centered">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<p>Page <span id="page-num">-</span> / <span id="page-count">-</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<div class="field has-addons">
|
||||
<span class="control">
|
||||
<input type="text" class="input" list="instrument-list" id="add-instrument-name"/>
|
||||
<datalist id="instrument-list">
|
||||
{% for inst in json_data.instruments.values %}
|
||||
<option value="{{inst}}"/>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
</span>
|
||||
<span class="control">
|
||||
<input type="number" class="input" max="4" min="1" size="3" id="add-instrument-variant"/>
|
||||
</span>
|
||||
<span class="control">
|
||||
<button class="button is-primary" onclick="addInstrument()">Add</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" style="display: inline-block;">
|
||||
<canvas id="inline-viewer" style="width: 500px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
|
||||
<div class="tag-grid">
|
||||
<div class="grid-column" id="page-list">
|
||||
</div>
|
||||
<div class="grid-column" id="tag-area">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ document.upload.name }}</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.7.570/pdf.min.js"
|
||||
integrity="sha512-g4FwCPWM/fZB1Eie86ZwKjOP+yBIxSBM/b2gQAiSVqCgkyvZ0XxYPDEcN2qqaKKEvK6a05+IPL1raO96RrhYDQ=="
|
||||
crossorigin="anonymous"></script>
|
||||
{{ json_data|json_script:"data" }}
|
||||
<script type="text/javascript">
|
||||
let url = "{{ document.upload.url|safe }}";
|
||||
|
||||
// Loaded via <script> tag, create shortcut to access PDF.js exports.
|
||||
let pdfjsLib = window['pdfjs-dist/build/pdf'];
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.7.570/pdf.worker.min.js';
|
||||
|
||||
// get current page tags
|
||||
let data = JSON.parse(document.getElementById('data').textContent);
|
||||
let tagArea = document.getElementById('tag-area');
|
||||
var dirty = false;
|
||||
|
||||
//document.getElementById('tag-list').onclick = (e) => setTag(e.target.dataset.tag);
|
||||
|
||||
var pdfDoc = null,
|
||||
pageNum = 1,
|
||||
pageRendering = false,
|
||||
pageNumPending = null,
|
||||
scale = 1,
|
||||
canvas = document.getElementById('inline-viewer'),
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
/**
|
||||
* Get page info from document, resize canvas accordingly, and render page.
|
||||
* @param num Page number.
|
||||
*/
|
||||
function renderPage(num) {
|
||||
pageRendering = true;
|
||||
// Using promise to fetch the page
|
||||
pdfDoc.getPage(num).then(function (page) {
|
||||
var viewport = page.getViewport({ scale: scale });
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
// Render PDF page into canvas context
|
||||
var renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
var renderTask = page.render(renderContext);
|
||||
|
||||
try {
|
||||
document.getElementById('grid-page-' + pageNum).classList.remove('is-active');
|
||||
} catch(e) {}
|
||||
|
||||
pageNum = num;
|
||||
document.getElementById('grid-page-' + pageNum).classList.add('is-active');
|
||||
|
||||
// Wait for rendering to finish
|
||||
renderTask.promise.then(function () {
|
||||
pageRendering = false;
|
||||
if (pageNumPending !== null) {
|
||||
// New page rendering is pending
|
||||
renderPage(pageNumPending);
|
||||
pageNumPending = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update page counters
|
||||
document.getElementById('page-num').textContent = num;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If another page rendering in progress, waits until the rendering is
|
||||
* finised. Otherwise, executes rendering immediately.
|
||||
*/
|
||||
function queueRenderPage(num) {
|
||||
if (pageRendering) {
|
||||
pageNumPending = num;
|
||||
} else {
|
||||
renderPage(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays previous page.
|
||||
*/
|
||||
function onPrevPage() {
|
||||
if (pageNum <= 1) {
|
||||
return;
|
||||
}
|
||||
pageNum--;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
//document.getElementById('prev').addEventListener('click', onPrevPage);
|
||||
|
||||
/**
|
||||
* Displays next page.
|
||||
*/
|
||||
function onNextPage() {
|
||||
if (pageNum >= pdfDoc.numPages) {
|
||||
return;
|
||||
}
|
||||
pageNum++;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
//document.getElementById('next').addEventListener('click', onNextPage);
|
||||
|
||||
/**
|
||||
* Asynchronously downloads PDF.
|
||||
*/
|
||||
pdfjsLib.getDocument(url).promise.then(function (pdfDoc_) {
|
||||
pdfDoc = pdfDoc_;
|
||||
|
||||
const pageList = document.getElementById('page-list');
|
||||
pageList.innerHTML = '';
|
||||
for (var i=0; i<pdfDoc.numPages; i++) {
|
||||
let page = i+1;
|
||||
let el = document.createElement('div');
|
||||
el.className = 'grid-page';
|
||||
el.id = 'grid-page-' + page;
|
||||
el.innerHTML = page;
|
||||
el.addEventListener('click', (evt) => {
|
||||
queueRenderPage(page);
|
||||
});
|
||||
pageList.appendChild(el);
|
||||
}
|
||||
|
||||
document.getElementById('page-count').textContent = pdfDoc.numPages;
|
||||
|
||||
// Initial/first page rendering
|
||||
renderPage(pageNum);
|
||||
|
||||
tagArea.innerHTML = '';
|
||||
for (let pageTag of data.pageTags) {
|
||||
addTag(pageTag[0], pageTag[1], pageTag[2]);
|
||||
}
|
||||
dirty = false;
|
||||
});
|
||||
|
||||
function addInstrument() {
|
||||
let name = document.getElementById('add-instrument-name');
|
||||
let variant = document.getElementById('add-instrument-variant');
|
||||
|
||||
let inst_name = name.value;
|
||||
var tag = null;
|
||||
for (let key in data.instruments) {
|
||||
if (data.instruments[key] == inst_name) {
|
||||
tag = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
alert("Unknown tag: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (variant.value) {
|
||||
tag += "-" + variant.value;
|
||||
}
|
||||
|
||||
variant.value = '';
|
||||
name.value = '';
|
||||
|
||||
addTag(tag, pageNum, pageNum);
|
||||
}
|
||||
|
||||
function addTag(tag, start, end) {
|
||||
console.log("addTag", tag, start, end);
|
||||
const el = document.createElement('div');
|
||||
el.className = 'grid-tag';
|
||||
el.dataset.tag = tag;
|
||||
el.dataset.start = start;
|
||||
el.dataset.end = end;
|
||||
|
||||
let setStart = document.createElement('span');
|
||||
setStart.className = "icon is-action";
|
||||
setStart.innerHTML = '<i class="fas fa-sort-amount-down" title="Set start page"></i>';
|
||||
setStart.addEventListener('click', () => setTagStart(el));
|
||||
el.appendChild(setStart);
|
||||
|
||||
|
||||
let label = document.createElement('span');
|
||||
|
||||
let name = document.createElement('b');
|
||||
name.innerHTML = get_instrument(tag);
|
||||
label.appendChild(name);
|
||||
|
||||
let del = document.createElement('span');
|
||||
del.className = "icon is-action";
|
||||
del.innerHTML = '<i class="fas fa-trash-alt" title="Remove this tag"></i>';
|
||||
del.addEventListener('click', () => {el.remove(), dirty=true});
|
||||
label.appendChild(del)
|
||||
|
||||
el.appendChild(label);
|
||||
|
||||
|
||||
let setEnd = document.createElement('span');
|
||||
setEnd.className = "icon is-action";
|
||||
setEnd.innerHTML = '<i class="fas fa-sort-amount-up" title="Set end page"></i>';
|
||||
setEnd.addEventListener('click', () => setTagEnd(el));
|
||||
el.appendChild(setEnd);
|
||||
|
||||
updateTag(el);
|
||||
tagArea.appendChild(el);
|
||||
}
|
||||
|
||||
function updateTag(tag) {
|
||||
let start = tag.dataset.start;
|
||||
let end = tag.dataset.end;
|
||||
let span = end-start+1;
|
||||
let height = span * 25 + (span-1) * 10;
|
||||
let top = (start-1) * 35;
|
||||
|
||||
tag.style.height = height + 'px';
|
||||
tag.style.marginTop = top + 'px';
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
function setTagStart(el) {
|
||||
if (pageNum > el.dataset.end) {
|
||||
alert("Select the first page and click extend");
|
||||
return;
|
||||
}
|
||||
el.dataset.start = pageNum;
|
||||
updateTag(el);
|
||||
}
|
||||
|
||||
function setTagEnd(el) {
|
||||
if (pageNum < el.dataset.start) {
|
||||
alert("Select the last page and click extend");
|
||||
return;
|
||||
}
|
||||
el.dataset.end = pageNum;
|
||||
updateTag(el);
|
||||
}
|
||||
|
||||
function saveTags() {
|
||||
const pageTags = [];
|
||||
for (let pageTag of tagArea.children ) {
|
||||
let start = pageTag.dataset.start;
|
||||
let end = pageTag.dataset.end;
|
||||
if (start == 1 && end == pdfDoc.numPages) {
|
||||
start = null;
|
||||
end = null;
|
||||
}
|
||||
pageTags.push([pageTag.dataset.tag, start, end])
|
||||
}
|
||||
|
||||
console.log(pageTags);
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}" },
|
||||
body: JSON.stringify(pageTags)
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.ok) {
|
||||
window.location = "{% url 'work_detail' document.work.pk %}"
|
||||
} else {
|
||||
alert("Failed: " + response.statusText)
|
||||
}
|
||||
});
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
function get_instrument(s) {
|
||||
let parts = s.split('-');
|
||||
let instrument = data.instruments[parts[0]];
|
||||
if (parts.length == 2) {
|
||||
return instrument + " " + parts[1];
|
||||
}
|
||||
return instrument;
|
||||
}
|
||||
|
||||
function checkSaved(e) {
|
||||
if (dirty) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
window.addEventListener('beforeunload', checkSaved);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,11 @@
|
||||
{% extends 'interface/project_base.html' %}
|
||||
|
||||
{% block page %}
|
||||
<h2>{{ folder.name }}</h2>
|
||||
<h3>Works</h3>
|
||||
<ul>
|
||||
{% for work in folder.works.all %}
|
||||
<li>{{ work.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,163 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<a class="button is-link" href="{% url 'item_list_manage' project.pk %}">
|
||||
<span class="icon"><i class="fas fa-list"></i></span>
|
||||
<span>Change items</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<form action="" method="post" target="_blank">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="field has-addons control">
|
||||
|
||||
<span class="control">
|
||||
<a class="button is-static">Your Instrument</a>
|
||||
</span>
|
||||
|
||||
<span class="control">
|
||||
<input type="text" class="input" list="instrument-list" id="instrument-name" onchange="updateParts()"/>
|
||||
<datalist id="instrument-list">
|
||||
{% for inst in instruments %}
|
||||
<option value="{{inst.1}}"/>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
</span>
|
||||
<span class="control">
|
||||
<span class="select" onchange="updateParts()">
|
||||
<select id="part-preference">
|
||||
<option value="0">Any</option>
|
||||
<option value="1">1st</option>
|
||||
<option value="2">2nd</option>
|
||||
<option value="3">3rd</option>
|
||||
<option value="4">4th</option>
|
||||
</select>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="control">
|
||||
<button type="submit" class="button is-primary">
|
||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||
<span>Get My Parts!</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th/>
|
||||
<th>Piece</th>
|
||||
<th>Running time</th>
|
||||
<th>Part</th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in object_list %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}.</td>
|
||||
<td>
|
||||
{% if request.is_admin %}
|
||||
<a href="{% url 'work_detail' item.work.pk %}">{{ item.work.name }}</a>
|
||||
{% else %}
|
||||
{{ item.work.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% firstof item.work.running_time "------" %}</td>
|
||||
<td class="select-cell">
|
||||
<input type="hidden" name="works" value="{{ item.work.pk }}"/>
|
||||
<span class="select is-small">
|
||||
<select name="instruments">
|
||||
<option value='-'>None</option>
|
||||
{% for part in item.work.digital_parts %}
|
||||
<option value='{{ part.tag }}'>{{ part.instrument }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href=""><i class="fas fa-download"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td/>
|
||||
<td/>
|
||||
<td>{{ running_time }}</td>
|
||||
<td/>
|
||||
<td>
|
||||
<button class="button is-link is-small" type="submit"><span class="icon"><i class="fas fa-copy"></i></span><span>Single combined PDF</span></button>
|
||||
<a class="button is-link is-small"><span class="icon"><i class="fas fa-archive"></i></span><span>Individual files (zipped)</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<p class="content">
|
||||
This page lets you download a complete set of music for your part by selecting your instrument
|
||||
and optionally a part (e.g. Flute 1 or 2).<br/>
|
||||
You can also tweak which parts you get for each piece, or click on a piece for more download options.
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ instruments|json_script:'instruments' }}
|
||||
<script type="text/javascript">
|
||||
|
||||
const INSTRUMENTS = JSON.parse(document.getElementById('instruments').innerText);
|
||||
|
||||
function updateParts() {
|
||||
var inst = document.getElementById("instrument-name").value;
|
||||
window.localStorage.setItem('instrument-name', inst);
|
||||
|
||||
for (let i of INSTRUMENTS) {
|
||||
if (i[1] === inst) inst = i[0];
|
||||
}
|
||||
|
||||
|
||||
let part = document.getElementById("part-preference").value;
|
||||
window.localStorage.setItem('part-preference', part);
|
||||
|
||||
|
||||
selectParts(inst, part);
|
||||
|
||||
}
|
||||
|
||||
function selectParts(inst, part) {
|
||||
|
||||
let prefix = inst + "-" + part;
|
||||
|
||||
let instruments = document.getElementsByName("instruments");
|
||||
for(let i=0; i<instruments.length; i++) {
|
||||
var result = "-"
|
||||
let options = instruments[i].children;
|
||||
for(let j=0; j<options.length; j++) {
|
||||
let value = options[j].value;
|
||||
if (value.startsWith(prefix)) {
|
||||
result = value;
|
||||
break;
|
||||
}
|
||||
if (result==="-" && value.startsWith(inst)) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
instruments[i].value = result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
document.getElementById("instrument-name").value = localStorage.getItem('instrument-name', 'All');
|
||||
document.getElementById("part-preference").value = localStorage.getItem('part-preference', 'Any');
|
||||
updateParts();
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,109 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'item_list_append' project.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
<span>Add</span>
|
||||
</a>
|
||||
<a href="#" onclick="save()" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-save"></i></span>
|
||||
<span>Save</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<table style="max-width: 600px; margin: 10pt auto;" class="zebra">
|
||||
<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;">
|
||||
<i class="fas fa-arrow-up clickable" title="Move up" onclick="moveItem({{ item.pk }}, -1)"></i>
|
||||
<i class="fas fa-arrow-down clickable" title="Move down" onclick="moveItem({{ item.pk }}, 1)"></i>
|
||||
<i class="fas fa-trash clickable" title="Remove" onClick="moveItem({{ item.pk }}, 0)"></i>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2">No items</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
|
||||
let workList = document.getElementById('work-list');
|
||||
var dirty = false;
|
||||
|
||||
function moveItem(pk, dir) {
|
||||
let items = Array.prototype.slice.call(workList.children, 0);
|
||||
var i=0;
|
||||
pk = "" + pk;
|
||||
for(i=0; i<items.length; i++) {
|
||||
if(items[i].dataset.pk === pk) break;
|
||||
}
|
||||
if(i >= items.length) return;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
let items = Array.prototype.slice.call(workList.children, 0);
|
||||
let data = {};
|
||||
for(var i=0; i<items.length; i++) {
|
||||
let item = items[i].dataset;
|
||||
data[item.pk] = item.order;
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}"},
|
||||
body: JSON.stringify(data)
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
dirty=false;
|
||||
window.location = "{% url 'item_list' project=project.pk %}";
|
||||
} else {
|
||||
alert("Failed: " + response.statusText)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function checkSaved(e) {
|
||||
if(dirty) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
window.addEventListener('beforeunload', checkSaved);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% with project.items.count as items %}
|
||||
{% if items %}
|
||||
<p>
|
||||
There {{ items|pluralize:"is,are" }} currently {{ items}} item{{ items|pluralize }} for this project.<br/>
|
||||
Click the '<a href="{% url 'item_list' project=project.pk %}">My Music</a>' button below to access your parts...
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
There are no items listed yet for this project - please check back later...
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
@@ -0,0 +1,9 @@
|
||||
<p class='menu-label'>Library</p>
|
||||
<ul class="menu-list">
|
||||
{% if project %}
|
||||
<li><a href="{% url 'item_list' project=project.pk %}">My Music</a></li>
|
||||
{% endif %}
|
||||
{% if request.is_admin %}
|
||||
<li><a href="{% url 'work_list' %}">Library</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@@ -0,0 +1,211 @@
|
||||
{% extends 'interface/project_base.html' %}
|
||||
{% load path_filters %}
|
||||
|
||||
{% block media %}
|
||||
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'work_edit' work.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-edit"></i></span>
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
<a href="{% url 'work_add_to_project' work.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
<span>Add to project</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<h3 class="title">
|
||||
{{ work.name }}
|
||||
{% for tag in work.tags %}
|
||||
<span class="tag is-success">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</h3>
|
||||
<p class="subtitle">{% firstof work.composer "Unattributed" %}{% if work.edition %} - {{ work.edition }}{% endif %}</p>
|
||||
<section class="block">
|
||||
<p class="block">{{ work.notes }}</p>
|
||||
|
||||
<p class="block">
|
||||
Location: <a href="{% url 'collection_work_list' work.collection.pk %}">{{ work.collection }}</a> [{{ work.identifier }}]<br/>
|
||||
Running time: {{ work.duration }}<br/>
|
||||
Licence: {{ work.get_licence_display }}<br/>
|
||||
{% for meta in work.meta %}
|
||||
{{ meta.get_name_display }}: {{ meta.value }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
|
||||
{% if work.parent %}
|
||||
<p>From <a href="{% url 'work_detail' work.parent.pk %}">{{ work.parent.name }} - {{ work.parent.composer }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if work.related_works.count %}
|
||||
<h3>Related</h3>
|
||||
<ul>
|
||||
{% for related in work.related_works.all %}
|
||||
<li><a href="{% url 'work_detail' related.pk %}">{{ related.name }} - {{ related.composer }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
<section class="block">
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<div class="box">
|
||||
<h4 class="subtitle is-size-4">
|
||||
<span class="icon"><i class="fas fa-book"></i></span>
|
||||
Printed Parts
|
||||
</h4>
|
||||
<div class="tags">
|
||||
{% for inst, c in work.physical_parts %}
|
||||
<span class="tag is-warning">{{ inst }} ({{ c }})</span>
|
||||
{% empty %}
|
||||
<p class="is-italic">No printed parts listed</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<div class="box">
|
||||
<h4 class="subtitle is-size-4">
|
||||
<span class="icon"><i class="fas fa-print"></i></span>
|
||||
Digital Parts
|
||||
</h4>
|
||||
<div class="tags">
|
||||
{% if work.digital_parts %}
|
||||
<a class="tag is-danger" href="{% url 'work_partset' pk=work.pk %}">Full Set</a>
|
||||
{% endif %}
|
||||
{% for part in work.digital_parts %}
|
||||
<a class="tag is-info" href="{% url 'part_download' pk=part.pk filename=part.filename %}"
|
||||
target="part_{{ part.pk }}" rel="">{{ part.instrument }}</a>
|
||||
{% empty %}
|
||||
<p class="is-italic">No digital parts available</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="block">
|
||||
<div class="box">
|
||||
<div class="level">
|
||||
<h4 class="subtitle is-size-4">
|
||||
<span class="icon"><i class="fas fa-file"></i></span>
|
||||
Files
|
||||
</h4>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Sections</th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="doc-list">
|
||||
{% for doc in work.docs.all %}
|
||||
<tr>
|
||||
<td><a href="{% url 'document_download' pk=doc.pk %}" target="_blank">
|
||||
{{ doc.upload.name|basename }}</a></td>
|
||||
<td>
|
||||
{% for part in doc.sections.all %}
|
||||
<a class="tag is-info" href="{% url 'part_download' pk=part.pk filename=part.filename %}">{{ part.instrument }}</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
{% if request.is_admin %}
|
||||
<a href="{% url 'document_annotate' pk=doc.pk %}"><i class="fas fa-tags"
|
||||
title="Manage Tags"></i></a>
|
||||
<a href=""><i class="fas fa-trash-alt" title="Delete Document"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if request.is_admin %}
|
||||
<div class="column is-one-quarter">
|
||||
<h4 class="is-size-5">Upload files</h4>
|
||||
<form action="{% url 'document_add' object.pk %}" class="dropzone" id="doc-upload">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% if request.is_admin %}
|
||||
<section class="block">
|
||||
<div class="box">
|
||||
<div class="level">
|
||||
<h4 class="is-size-4">
|
||||
<span class="icon"><i class="fas fa-book-reader"></i></span>
|
||||
Loans
|
||||
</h4>
|
||||
<span class="level-right">
|
||||
<a class="icon-text" href="{% url 'work_add_to_project' work.pk %}"><span class="icon"><i
|
||||
class="fas fa-plus-circle"></i></span> Checkout</a>
|
||||
</span>
|
||||
</div>
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ensemble</th>
|
||||
<th>Project</th>
|
||||
<th>Checked Out</th>
|
||||
<th>Due Back</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in work.current_loans %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ensemble_detail' item.project.ensemble_id %}">
|
||||
{{ item.project.ensemble.name }}
|
||||
</a></td>
|
||||
<td><a href="{% url 'project_detail' item.project.pk %}">{{ item.project.name }}</a></td>
|
||||
<td>{{ item.checkout.date|date:"d/m/Y" }}</td>
|
||||
<td>{{ item.due.date|date:"d/m/Y" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td>No current loans</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
Dropzone.options.docUpload = { // camelized version of the `id`
|
||||
paramName: "upload", // The name that will be used to transfer the file
|
||||
maxFilesize: 12, // MB
|
||||
createImageThumbnails: false,
|
||||
thumbnailWidth: 60,
|
||||
thumbnailHeight: 60,
|
||||
init: function () {
|
||||
this.on("complete", file => {
|
||||
console.log(file);
|
||||
let data = JSON.parse(file.xhr.response);
|
||||
console.log(data);
|
||||
let tbody = document.getElementById('doc-list');
|
||||
let tr = document.createElement('tr');
|
||||
tr.innerHTML = data.entry;
|
||||
tbody.appendChild(tr);
|
||||
this.removeFile(file);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,71 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load url_tools %}
|
||||
|
||||
{% block admin %}
|
||||
{% if collection_id %}
|
||||
<a href="{% url 'work_add' pk=collection_id %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
<span>Add a work</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<form method="GET">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" name="filter" type="text" placeholder="Filter" value="{{ request.GET.filter }}"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button" href="?"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Work</th>
|
||||
<th>Composer</th>
|
||||
<th class="is-hidden-mobile">Edition</th>
|
||||
{% if request.is_admin %}
|
||||
<th class="is-hidden-touch">Collection</th>
|
||||
<th class="is-hidden-mobile">Copies</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for work in object_list %}
|
||||
<tr>
|
||||
<td><a href="{% url 'work_detail' pk=work.pk %}">{{ work.name }}</a></td>
|
||||
<td title="{{ work.composer }}">{{ work.composer|truncatewords:3 }}</td>
|
||||
<td class="is-hidden-mobile" title="{{ work.edition }}">{{ work.edition|truncatewords:2 }}</td>
|
||||
{% if request.is_admin %}
|
||||
<td class="is-hidden-touch">{{ work.collection.name }}</td>
|
||||
<td class="is-hidden-mobile {{ work.is_available|yesno:'has-text-success,has-text-danger' }}">{{ work.available }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No works found</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
<a class="pagination-previous" href="{% url_update page=page_obj.prev_page_number %}">Previous</a>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<a class="pagination-next" href="{% url_update page=page_obj.next_page_number %}">Next page</a>
|
||||
{% endif %}
|
||||
<ul class="pagination-list">
|
||||
{% for page in page_obj.paginator.page_range %}
|
||||
<li>
|
||||
<a class="pagination-link {% if forloop.counter == page_obj.number %}is-current{% endif %}" href="{% url_update page=forloop.counter %}" aria-label="Goto page {{ forloop.counter }}">{{ forloop.counter }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
|
||||
|
||||
|
||||
{% block page %}
|
||||
|
||||
<form action="" method="post" target="_blank">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="admin-tools is-pulled-right">
|
||||
<button type="submit" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-print"></i></span>
|
||||
<span>Print Set</span>
|
||||
</button>
|
||||
<a class="button is-link is-light" href="{% url 'work_detail' pk=object.pk %}">
|
||||
<span>Cancel</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 class="subtitle"><a href="{% url 'work_detail' pk=work.pk %}">{{ work.name }}</a></h3>
|
||||
|
||||
<p class="block">
|
||||
You can generate a custom partset for printing - select the number of copies of each you want...
|
||||
</p>
|
||||
|
||||
<div class="columns is-multiline is-mobile">
|
||||
{% for part in work.digital_parts %}
|
||||
<div class="column is-3 has-text-right">
|
||||
<span style="white-space: nowrap">{{ part.instrument }}</span>
|
||||
<input name="parts" type="hidden" value="{{ part.tag }}">
|
||||
<input name="copies" type="number" value="{% if part.tag == 'Score' %}0{% else %}1{% endif %}" size="1">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user