Rewrote tags code in annotate (with help from OpenCode)
This commit is contained in:
parent
fefa0a28b6
commit
4a22264d3f
@ -366,8 +366,8 @@ class Work(models.Model):
|
|||||||
|
|
||||||
def music_tags(self):
|
def music_tags(self):
|
||||||
tags = dict(self.orchestration.as_list())
|
tags = dict(self.orchestration.as_list())
|
||||||
for section in Section.objects.filter(doc__work_id=self.pk):
|
# for section in Section.objects.filter(doc__work_id=self.pk):
|
||||||
tags.setdefault(section.tag, section.name)
|
# tags.setdefault(section.tag, section.name)
|
||||||
|
|
||||||
return tags.items()
|
return tags.items()
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import re
|
|||||||
GENERAL = """
|
GENERAL = """
|
||||||
mvmt Movement
|
mvmt Movement
|
||||||
ex Excerpt
|
ex Excerpt
|
||||||
sec Section
|
sect Section
|
||||||
pce Piece
|
pce Piece
|
||||||
no Number
|
no No.
|
||||||
|
page Page
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_MusicTags
|
# taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_MusicTags
|
||||||
|
|||||||
@ -14,42 +14,97 @@
|
|||||||
|
|
||||||
{% block media %}
|
{% block media %}
|
||||||
<style>
|
<style>
|
||||||
|
#unassigned-area {
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
#unassigned-area .tag {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#area-left h3 {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.tag-grid {
|
.tag-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.grid-column {
|
.grid-column {
|
||||||
margin-left: 1em;
|
margin-left: 0;
|
||||||
margin-right: 1em;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
.grid-page {
|
.grid-page {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
margin-bottom: 5px;
|
border: none;
|
||||||
border: 1px solid #999;
|
border-top: 1px solid #DDD;
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.grid-page:last-child {
|
||||||
|
border-bottom: 1px solid #DDD;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
.grid-page.is-active {
|
.grid-page.is-active {
|
||||||
background-color: var(--primary);
|
/*background-color: var(--bulma-link-light);*/
|
||||||
color: white;
|
color: var(--bulma-link);
|
||||||
|
background-color: #EEE;
|
||||||
|
border-radius: 0px 6px 6px 0px;
|
||||||
}
|
}
|
||||||
.grid-tag {
|
.grid-tag {
|
||||||
border: 2px solid var(--primary);
|
background: #fff;
|
||||||
background-color: rgba(240, 240, 240, 0.5);
|
border: 1px solid #DDD;
|
||||||
border-radius: 5px;
|
border-radius: 0px 4px 4px 0px;
|
||||||
padding-left: 1em;
|
border-left: none;
|
||||||
padding-right: 1em;
|
padding-left: 0.5em;
|
||||||
margin-bottom: 5px;
|
min-width: 100px;
|
||||||
min-width: 200px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: start;
|
align-items: top;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 4px 0 6px -4px rgba(0,0,0,0.12);
|
||||||
}
|
}
|
||||||
|
.grid-tag:hover {
|
||||||
|
z-index: 100 !important;
|
||||||
|
}
|
||||||
|
.tag-handle {
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
background-color: var(--bulma-warning-light);
|
||||||
|
margin-left: 10px;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
.tag-name {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.tag-handle-resize {
|
||||||
|
position: absolute;
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
border-radius: 3px 0px 0px 0px;
|
||||||
|
background-color: var(--bulma-warning);
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for tag, colour in tag_colours %}
|
||||||
|
.tag-type-{{ tag }} .tag-handle {
|
||||||
|
background-color: var(--bulma-{{ colour }}-light);
|
||||||
|
}
|
||||||
|
.tag-type-{{ tag }} .tag-handle-resize {
|
||||||
|
background-color: var(--bulma-{{ colour }});
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
#tag-area {
|
#tag-area {
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
|
position: relative;
|
||||||
|
font-size: 9pt;
|
||||||
}
|
}
|
||||||
.instrument {
|
.instrument {
|
||||||
display: list-item !important;
|
display: list-item !important;
|
||||||
@ -64,32 +119,34 @@
|
|||||||
{% block page %}
|
{% block page %}
|
||||||
<h3 class="subtitle"><a href="{% url 'work_detail' collection.pk document.work_id %}">{{ document.work.name }}</a></h3>
|
<h3 class="subtitle"><a href="{% url 'work_detail' collection.pk document.work_id %}">{{ document.work.name }}</a></h3>
|
||||||
<div id="annotation-area" class="columns is-centered">
|
<div id="annotation-area" class="columns is-centered">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow" id="area-left">
|
||||||
|
<h3>Page control</h3>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<button class="button is-small is-primary" id="prev"><</button>
|
<button class="button is-small is-primary" id="prev"><</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column has-text-centered">
|
||||||
<span id="page-num">-</span> / <span id="page-count">-</span>
|
<span id="page-num">-</span> / <span id="page-count">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<button class="button is-small is-primary" id="next">></button>
|
<button class="button is-small is-primary" id="next">></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Quick Tags</h3>
|
||||||
<ul id="unassigned-area">
|
<ul id="unassigned-area">
|
||||||
{% for tag, inst in document.work.music_tags %}
|
{% for tag, inst in document.work.music_tags %}
|
||||||
<li>
|
<li class="tag is-warning" onclick="assignTag('{{tag}}', this)">{{ inst }}</li>
|
||||||
<span class="is-clickable" onclick="assignInstrument('{{tag}}', this)")>{{ inst }}</span>
|
|
||||||
|
|
||||||
<span class="is-clickable" onclick="addNumberedInstrument('{{tag}}', this)">...</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<a onclick="document.getElementById('add-modal').classList.add('is-active')">Add Tag</a>
|
<h3>Actions</h3>
|
||||||
|
<div>
|
||||||
|
<a class="button is-small is-primary" onclick="showAddModal()">New Tag</a>
|
||||||
|
<button class="button is-small is-primary" onclick="expandEntries()">Expand Tags</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<div class="box" style="display: inline-block;">
|
<div class="box" style="display: inline-block; padding: 0px;">
|
||||||
<canvas id="inline-viewer" style="width: 500px;"></canvas>
|
<canvas id="inline-viewer" style="width: 500px;"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,7 +159,6 @@
|
|||||||
<div class="grid-column" id="tag-area">
|
<div class="grid-column" id="tag-area">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="button is-primary" style="width: 100%" onclick="expandEntries()">Expand</button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -111,24 +167,46 @@
|
|||||||
<div class="modal-background" onclick="closeAddModal()"></div>
|
<div class="modal-background" onclick="closeAddModal()"></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
<p class="modal-card-title">Add Instrument</p>
|
<p class="modal-card-title">Add Tag</p>
|
||||||
<button class="delete" aria-label="close" onclick="closeAddModal()"></button>
|
<button class="delete" aria-label="close" onclick="closeAddModal()"></button>
|
||||||
</header>
|
</header>
|
||||||
<section class="modal-card-body">
|
<section class="modal-card-body">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons is-justify-content-center">
|
||||||
<span class="control">
|
<span class="control">
|
||||||
<input type="text" class="input" list="instrument-list" id="add-instrument-name"/>
|
<span class="select">
|
||||||
|
<select onChange="changeTagType()" id="tag-type-selection">
|
||||||
|
<option value="inst">Instrument</option>
|
||||||
|
<option value="mvmt">Movement</option>
|
||||||
|
<option value="sect">Section</option>
|
||||||
|
<option value="no">Number</option>
|
||||||
|
<option value="page">Page</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="control add-tag-option enable-page">
|
||||||
|
<span class="select">
|
||||||
|
<select id="add-tag-page">
|
||||||
|
<option>Left</option>
|
||||||
|
<option>Right</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="control add-tag-option enable-sect">
|
||||||
|
<input type="text" class="input" id="add-tag-name"/>
|
||||||
|
</span>
|
||||||
|
<span class="control add-tag-option enable-inst">
|
||||||
|
<input type="text" class="input" list="instrument-list" id="add-tag-inst"/>
|
||||||
<datalist id="instrument-list">
|
<datalist id="instrument-list">
|
||||||
{% for inst in json_data.instruments.values %}
|
{% for inst in json_data.instruments.values %}
|
||||||
<option value="{{inst}}"/>
|
<option value="{{inst}}"/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</datalist>
|
</datalist>
|
||||||
</span>
|
</span>
|
||||||
<span class="control">
|
<span class="control add-tag-option enable-inst enable-mvmt enable-no">
|
||||||
<input type="number" class="input" max="4" min="1" size="3" id="add-instrument-variant"/>
|
<input type="number" class="input" min="1" size="3" id="add-tag-ordinal"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="control">
|
<span class="control">
|
||||||
<button class="button is-primary" onclick="addInstrument();">Add</button>
|
<button class="button is-primary" onclick="createNewTag();">Add</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -137,6 +215,15 @@
|
|||||||
|
|
||||||
<p>{{ document.upload.name }}</p>
|
<p>{{ document.upload.name }}</p>
|
||||||
|
|
||||||
|
<template id="blank-tag">
|
||||||
|
<div class="grid-tag">
|
||||||
|
<span class="tag-name"></span>
|
||||||
|
<div class="tag-handle">
|
||||||
|
<div class="tag-handle-resize"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
@ -166,7 +253,11 @@
|
|||||||
pageNumPending = null,
|
pageNumPending = null,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
canvas = document.getElementById('inline-viewer'),
|
canvas = document.getElementById('inline-viewer'),
|
||||||
ctx = canvas.getContext('2d');
|
ctx = canvas.getContext('2d'),
|
||||||
|
draggedTag = null,
|
||||||
|
dragMode = null,
|
||||||
|
dragOrigStart = null,
|
||||||
|
dragOrigEnd = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get page info from document, resize canvas accordingly, and render page.
|
* Get page info from document, resize canvas accordingly, and render page.
|
||||||
@ -251,6 +342,7 @@
|
|||||||
pdfDoc = pdfDoc_;
|
pdfDoc = pdfDoc_;
|
||||||
|
|
||||||
const pageList = document.getElementById('page-list');
|
const pageList = document.getElementById('page-list');
|
||||||
|
const tagGrid = document.querySelector('.tag-grid');
|
||||||
pageList.innerHTML = '';
|
pageList.innerHTML = '';
|
||||||
for (var i=0; i<pdfDoc.numPages; i++) {
|
for (var i=0; i<pdfDoc.numPages; i++) {
|
||||||
let page = i+1;
|
let page = i+1;
|
||||||
@ -264,10 +356,47 @@
|
|||||||
pageList.appendChild(el);
|
pageList.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tagGrid.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
if (draggedTag) {
|
||||||
|
const rect = pageList.getBoundingClientRect();
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
const page = Math.min(Math.max(Math.floor(y / 25) + 1, 1), pdfDoc.numPages);
|
||||||
|
if (dragMode === 'resize') {
|
||||||
|
if (page >= parseInt(draggedTag.dataset.start) && page !== parseInt(draggedTag.dataset.end)) {
|
||||||
|
draggedTag.dataset.end = page;
|
||||||
|
updateTag(draggedTag);
|
||||||
|
resolveOverlaps();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const length = dragOrigEnd - dragOrigStart;
|
||||||
|
if (page !== parseInt(draggedTag.dataset.start)) {
|
||||||
|
draggedTag.dataset.start = page;
|
||||||
|
draggedTag.dataset.end = Math.min(page + length, pdfDoc.numPages);
|
||||||
|
updateTag(draggedTag);
|
||||||
|
resolveOverlaps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tagGrid.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (draggedTag) {
|
||||||
|
draggedTag = null;
|
||||||
|
dragMode = null;
|
||||||
|
}
|
||||||
|
resolveOverlaps();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('page-count').textContent = pdfDoc.numPages;
|
document.getElementById('page-count').textContent = pdfDoc.numPages;
|
||||||
|
|
||||||
// Initial/first page rendering
|
// Initial/first page rendering
|
||||||
renderPage(pageNum);
|
renderPage(pageNum);
|
||||||
|
setTimeout(() => {
|
||||||
|
const previewBox = document.querySelector('.box');
|
||||||
|
tagGrid.style.maxHeight = previewBox.offsetHeight + 'px';
|
||||||
|
}, 100);
|
||||||
|
|
||||||
tagArea.innerHTML = '';
|
tagArea.innerHTML = '';
|
||||||
for (let pageTag of data.pageTags) {
|
for (let pageTag of data.pageTags) {
|
||||||
@ -276,10 +405,13 @@
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showAddModal() {
|
||||||
|
document.getElementById("add-modal").classList.add("is-active");
|
||||||
|
changeTagType();
|
||||||
|
}
|
||||||
|
|
||||||
function closeAddModal() {
|
function closeAddModal() {
|
||||||
document.getElementById('add-modal').classList.remove('is-active');
|
document.getElementById('add-modal').classList.remove('is-active');
|
||||||
document.getElementById('add-instrument-name').value = "";
|
|
||||||
document.getElementById('add-instrument-variant').value = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNumberedInstrument(tag, e) {
|
function addNumberedInstrument(tag, e) {
|
||||||
@ -292,11 +424,27 @@
|
|||||||
modal.classList.add('is-active');
|
modal.classList.add('is-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addInstrument() {
|
function changeTagType() {
|
||||||
let name = document.getElementById('add-instrument-name');
|
const tag_select = document.querySelector("#tag-type-selection");
|
||||||
let variant = document.getElementById('add-instrument-variant');
|
const selected = tag_select.value;
|
||||||
|
document.querySelectorAll(".add-tag-option").forEach((x) => {x.classList.add("is-hidden")});
|
||||||
|
document.querySelectorAll(".add-tag-option input").forEach((x) => {x.value=""});
|
||||||
|
document.querySelectorAll(".enable-" + selected).forEach((x) => {x.classList.remove("is-hidden")});
|
||||||
|
tag_select.focus();
|
||||||
|
|
||||||
let inst_name = name.value;
|
}
|
||||||
|
|
||||||
|
function generateNewTag() {
|
||||||
|
const selected = document.querySelector("#tag-type-selection").value;
|
||||||
|
const n = document.querySelector("#add-tag-ordinal").value;
|
||||||
|
switch (selected) {
|
||||||
|
case "mvmt":
|
||||||
|
case "no":
|
||||||
|
return selected + "-" + n;
|
||||||
|
case "sect":
|
||||||
|
return "sect-" + document.querySelector("#add-tag-name").value;
|
||||||
|
case "inst":
|
||||||
|
const inst_name = document.querySelector("#add-tag-inst").value;
|
||||||
var tag = null;
|
var tag = null;
|
||||||
for (let key in data.instruments) {
|
for (let key in data.instruments) {
|
||||||
if (data.instruments[key] == inst_name) {
|
if (data.instruments[key] == inst_name) {
|
||||||
@ -306,83 +454,172 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
alert("Unknown tag: " + name);
|
tag = inst_name;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variant.value) {
|
if (n) {
|
||||||
tag += "-" + variant.value;
|
tag += "-" + n;
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
case "page":
|
||||||
|
return "page-" + document.querySelector("#add-tag-page").value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variant.value = '';
|
|
||||||
name.value = '';
|
|
||||||
|
|
||||||
let li = document.createElement('li');
|
function createNewTag() {
|
||||||
li.classList.add("is-clickable");
|
|
||||||
li.addEventListener('click', () => assignInstrument(tag, li));
|
const tag = generateNewTag()
|
||||||
li.innerHTML = get_instrument(tag);
|
|
||||||
document.getElementById('unassigned-area').appendChild(li);
|
|
||||||
|
|
||||||
addTag(tag, pageNum, pageNum);
|
addTag(tag, pageNum, pageNum);
|
||||||
closeAddModal();
|
closeAddModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignInstrument(tag, el) {
|
function assignTag(tag, el) {
|
||||||
addTag(tag, pageNum, pageNum);
|
addTag(tag, pageNum, pageNum);
|
||||||
//el.remove();
|
//el.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTag(tag, start, end) {
|
function addTag(tag, start, end) {
|
||||||
console.log("addTag", tag, start, end);
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.className = 'grid-tag';
|
//console.log("addTag", tag, start, end);
|
||||||
el.dataset.tag = tag;
|
const fragment = document.importNode(document.querySelector("#blank-tag").content, true);
|
||||||
|
const el = fragment.querySelector("div");
|
||||||
|
const tagName = el.querySelector(".tag-name");
|
||||||
|
tagName.innerHTML=get_tag_name(tag);
|
||||||
|
|
||||||
|
//el.className = 'grid-tag';
|
||||||
el.dataset.start = start;
|
el.dataset.start = start;
|
||||||
el.dataset.end = end;
|
el.dataset.end = end;
|
||||||
|
el.dataset.tag = tag;
|
||||||
|
|
||||||
let setStart = document.createElement('span');
|
const parts = tag.split("-")
|
||||||
setStart.className = "icon is-action";
|
el.classList.add("tag-type-" + parts[0]);
|
||||||
setStart.innerHTML = '<span class="material-symbols-outlined" title="Set start page">vertical_align_top</span>';
|
|
||||||
setStart.addEventListener('click', () => setTagStart(el));
|
|
||||||
el.appendChild(setStart);
|
|
||||||
|
|
||||||
|
|
||||||
let label = document.createElement('span');
|
const handle = el.querySelector('.tag-handle');
|
||||||
|
handle.draggable = true;
|
||||||
let name = document.createElement('b');
|
handle.addEventListener('dragstart', (e) => {
|
||||||
name.innerHTML = get_instrument(tag);
|
draggedTag = el;
|
||||||
label.appendChild(name);
|
dragMode = 'move';
|
||||||
el.appendChild(label);
|
dragOrigStart = parseInt(el.dataset.start);
|
||||||
|
dragOrigEnd = parseInt(el.dataset.end);
|
||||||
let del = document.createElement('span');
|
el.style.zIndex = 1000;
|
||||||
del.className = "icon is-action";
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
del.innerHTML = '<span class="material-symbols-outlined" title="Remove this tag">delete</span>';
|
const blankImg = new Image();
|
||||||
del.addEventListener('click', () => {
|
blankImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
el.remove();
|
e.dataTransfer.setDragImage(blankImg, 0, 0);
|
||||||
dirty=true;
|
});
|
||||||
|
handle.addEventListener('dragend', () => {
|
||||||
|
if (draggedTag === el && dragMode === 'move') {
|
||||||
|
el.dataset.start = dragOrigStart;
|
||||||
|
el.dataset.end = dragOrigEnd;
|
||||||
|
updateTag(el);
|
||||||
|
el.remove();
|
||||||
|
dirty = true;
|
||||||
|
} else if (draggedTag === el) {
|
||||||
|
el.dataset.start = dragOrigStart;
|
||||||
|
el.dataset.end = dragOrigEnd;
|
||||||
|
updateTag(el);
|
||||||
|
el.style.zIndex = '';
|
||||||
|
}
|
||||||
|
resolveOverlaps();
|
||||||
|
draggedTag = null;
|
||||||
|
dragMode = null;
|
||||||
});
|
});
|
||||||
el.appendChild(del)
|
|
||||||
|
|
||||||
let setEnd = document.createElement('span');
|
const resize = el.querySelector('.tag-handle-resize');
|
||||||
setEnd.className = "icon is-action";
|
resize.draggable = true;
|
||||||
setEnd.innerHTML = '<span class="material-symbols-outlined" title="Set end page">vertical_align_bottom</span>';
|
resize.addEventListener('dragstart', (e) => {
|
||||||
setEnd.addEventListener('click', () => setTagEnd(el));
|
draggedTag = el;
|
||||||
el.appendChild(setEnd);
|
dragMode = 'resize';
|
||||||
|
dragOrigEnd = parseInt(el.dataset.end);
|
||||||
|
el.style.zIndex = 1000;
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
const blankImg2 = new Image();
|
||||||
|
blankImg2.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
|
e.dataTransfer.setDragImage(blankImg2, 0, 0);
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
resize.addEventListener('dragend', () => {
|
||||||
|
if (draggedTag === el) {
|
||||||
|
el.dataset.end = dragOrigEnd;
|
||||||
|
updateTag(el);
|
||||||
|
el.style.zIndex = '';
|
||||||
|
}
|
||||||
|
resolveOverlaps();
|
||||||
|
draggedTag = null;
|
||||||
|
dragMode = null;
|
||||||
|
});
|
||||||
|
|
||||||
updateTag(el);
|
updateTag(el);
|
||||||
tagArea.appendChild(el);
|
tagArea.appendChild(el);
|
||||||
|
resolveOverlaps();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTag(tag) {
|
function updateTag(tag) {
|
||||||
let start = tag.dataset.start;
|
let start = tag.dataset.start;
|
||||||
let end = tag.dataset.end;
|
let end = tag.dataset.end;
|
||||||
let span = end-start+1;
|
let span = end-start+1;
|
||||||
let height = span * 25 + (span-1) * 5;
|
let height = span * 25 + 1;
|
||||||
let top = (start-1) * 30;
|
let top = (start-1) * 25;
|
||||||
|
|
||||||
tag.style.height = height + 'px';
|
tag.style.height = height + 'px';
|
||||||
tag.style.marginTop = top + 'px';
|
tag.style.marginTop = top + 'px';
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
|
||||||
|
const name = tag.querySelector('.tag-name');
|
||||||
|
const handle = tag.querySelector('.tag-handle');
|
||||||
|
if (name) {
|
||||||
|
if (span >= 3) {
|
||||||
|
name.style.position = 'absolute';
|
||||||
|
name.style.top = '4px';
|
||||||
|
name.style.right = '20px';
|
||||||
|
name.style.writingMode = 'vertical-rl';
|
||||||
|
tag.style.justifyContent = 'flex-end';
|
||||||
|
if (handle) handle.style.marginLeft = 'auto';
|
||||||
|
} else {
|
||||||
|
name.style.position = '';
|
||||||
|
name.style.top = '';
|
||||||
|
name.style.right = '';
|
||||||
|
name.style.writingMode = '';
|
||||||
|
tag.style.justifyContent = '';
|
||||||
|
if (handle) handle.style.marginLeft = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveOverlaps() {
|
||||||
|
const tags = Array.from(tagArea.children);
|
||||||
|
tags.sort((a, b) => parseInt(a.dataset.start) - parseInt(b.dataset.start));
|
||||||
|
|
||||||
|
const groups = [];
|
||||||
|
let current = [tags[0]];
|
||||||
|
for (let i = 1; i < tags.length; i++) {
|
||||||
|
const prevEnd = parseInt(tags[i-1].dataset.end);
|
||||||
|
const currStart = parseInt(tags[i].dataset.start);
|
||||||
|
if (currStart <= prevEnd) {
|
||||||
|
current.push(tags[i]);
|
||||||
|
} else {
|
||||||
|
groups.push(current);
|
||||||
|
current = [tags[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current.length) groups.push(current);
|
||||||
|
|
||||||
|
for (let group of groups) {
|
||||||
|
group.sort((a, b) => {
|
||||||
|
const spanA = parseInt(a.dataset.end) - parseInt(a.dataset.start);
|
||||||
|
const spanB = parseInt(b.dataset.end) - parseInt(b.dataset.start);
|
||||||
|
return spanB - spanA;
|
||||||
|
});
|
||||||
|
const n = group.length;
|
||||||
|
group.forEach((tag, i) => {
|
||||||
|
tag.style.minWidth = (120 + (n - 1 - i) * 70) + 'px';
|
||||||
|
tag.style.zIndex = i;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTagStart(el) {
|
function setTagStart(el) {
|
||||||
@ -404,6 +641,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function expandEntries() {
|
function expandEntries() {
|
||||||
|
const answer = confirm("This will expand all tags to fill up available pages");
|
||||||
|
if (!answer) return;
|
||||||
|
|
||||||
|
|
||||||
const entries = Array.from(tagArea.children);
|
const entries = Array.from(tagArea.children);
|
||||||
entries.sort((a, b) => a.dataset.start-b.dataset.start);
|
entries.sort((a, b) => a.dataset.start-b.dataset.start);
|
||||||
const c = entries.length;
|
const c = entries.length;
|
||||||
@ -416,6 +657,33 @@
|
|||||||
updateTag(entries[c-1]);
|
updateTag(entries[c-1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const body = document.querySelector("body");
|
||||||
|
function keymap(e) {
|
||||||
|
if (e.target != body) return;
|
||||||
|
switch(e.key) {
|
||||||
|
case "ArrowRight":
|
||||||
|
case "l":
|
||||||
|
case "ArrowDown":
|
||||||
|
case "j":
|
||||||
|
onNextPage();
|
||||||
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
case "h":
|
||||||
|
case "ArrowUp":
|
||||||
|
case "k":
|
||||||
|
onPrevPage();
|
||||||
|
break;
|
||||||
|
case "n":
|
||||||
|
showAddModal();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
}
|
||||||
|
body.addEventListener("keydown", keymap)
|
||||||
|
|
||||||
function saveTags() {
|
function saveTags() {
|
||||||
const pageTags = [];
|
const pageTags = [];
|
||||||
for (let pageTag of tagArea.children ) {
|
for (let pageTag of tagArea.children ) {
|
||||||
@ -444,9 +712,9 @@
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_instrument(s) {
|
function get_tag_name(s) {
|
||||||
let parts = s.split('-');
|
let parts = s.split('-');
|
||||||
let instrument = data.instruments[parts[0]];
|
let instrument = data.instruments[parts[0]] || parts[0];
|
||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
return instrument + " " + parts[1];
|
return instrument + " " + parts[1];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,6 @@ class ProjectItemListView(ProjectMixin, ListView):
|
|||||||
model = models.ProjectItem
|
model = models.ProjectItem
|
||||||
|
|
||||||
def post(self, request: HttpRequest, **kwargs):
|
def post(self, request: HttpRequest, **kwargs):
|
||||||
|
|
||||||
project_works = self.project.works.all()
|
project_works = self.project.works.all()
|
||||||
|
|
||||||
print(request.POST)
|
print(request.POST)
|
||||||
@ -597,6 +596,12 @@ class DocumentAnnotateView(DocumentMixin, DetailView):
|
|||||||
collection=data["collection"].pk,
|
collection=data["collection"].pk,
|
||||||
pk=data["document"].pk,
|
pk=data["document"].pk,
|
||||||
)
|
)
|
||||||
|
data["tag_colours"] = [
|
||||||
|
("page", "danger"),
|
||||||
|
("no", "link"),
|
||||||
|
("mvmt", "link"),
|
||||||
|
("sect", "success"),
|
||||||
|
]
|
||||||
|
|
||||||
data["json_data"] = {"pageTags": pages, "instruments": dict(MUSIC_TAGS)}
|
data["json_data"] = {"pageTags": pages, "instruments": dict(MUSIC_TAGS)}
|
||||||
return data
|
return data
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user