389 lines
10 KiB
HTML
389 lines
10 KiB
HTML
{% 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 %} |