Compare commits

..

No commits in common. "6a7aa4223736addb3fdbc01ecab863d479d90654" and "375d08488d24688b9a49b0b6ae7da3804ef6c9f4" have entirely different histories.

25 changed files with 303 additions and 534 deletions

View File

@ -1,9 +1,5 @@
PYTHON=env/bin/python
LIBRARY=polyphonic/interface/static
DROPZONE=5.7.0
BULMA=1.0.4
ALPINE=3.15.12
VERSION=0.8.4
@ -43,18 +39,7 @@ upgrade:
poetry run manage collectstatic
${MAKE} libraries
#libraries: static/dropzone static/fonts/Quicksand_Book.otf
libraries: ${LIBRARY}/css/bulma.min.css ${LIBRARY}/js/alpine.js ${LIBRARY}/css/material.css
${LIBRARY}/css/bulma.min.css:
curl -o $@ "https://cdn.jsdelivr.net/npm/bulma@${BULMA}/css/bulma.min.css"
${LIBRARY}/js/alpine.js:
curl -o $@ "https://cdn.jsdelivr.net/npm/alpinejs@${ALPINE}/dist/cdn.min.js"
${LIBRARY}/css/material.css:
curl -o $@ "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
libraries: static/dropzone static/fonts/Quicksand_Book.otf
static/dropzone:
wget -O dropzone-${DROPZONE}.zip https://github.com/enyo/dropzone/archive/v${DROPZONE}.zip

View File

@ -31,12 +31,12 @@ class ProjectForm(forms.ModelForm, BaseForm):
class Meta:
model = models.Project
fields = ["name", "description", "modules", "event_date"]
widgets = {"event_date": forms.DateTimeInput()}
# widgets = {
# 'event_date': forms.DateTimeInput(attrs={'type': 'date'})
# }
modules = forms.MultipleChoiceField(
choices=[
(x, x.replace(".", " ").title()) for x in models.settings.POLYPHONIC_MODULES
],
choices=[(x, x.title()) for x in models.settings.POLYPHONIC_MODULES],
widget=forms.CheckboxSelectMultiple,
required=False,
)

View File

@ -192,9 +192,8 @@ class Project(models.Model):
class Module(models.Model):
"""Enable modules on a oriject"""
name = models.CharField(
max_length=100,
choices=[(x, x.replace(".", " ").title()) for x in settings.POLYPHONIC_MODULES],
name = models.SlugField(
max_length=20, choices=[(x, x.title()) for x in settings.POLYPHONIC_MODULES]
)
project = models.ForeignKey(
Project, related_name="modules", on_delete=models.CASCADE

View File

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 426 KiB

View File

Before

Width:  |  Height:  |  Size: 258 B

After

Width:  |  Height:  |  Size: 258 B

View File

@ -5,11 +5,15 @@
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="{% static 'images/icon.png' %}" />
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/polyphonic.css' %}" />
<link rel="stylesheet" href="{% static 'css/material.css' %}" />
<script src="{% static 'js/interface.js' %}"></script>
<link rel="icon" type="image/png" href="{% static 'interface/icon.png' %}" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
<!--link rel="stylesheet" href="{% static 'interface/css/bulmaswatch.min.css' %}"></link-->
<link rel="stylesheet" href="{% static 'interface/css/polyphonic.css' %}"></link>
<script src="{% static 'interface/js/interface.js' %}"></script>
<script src="//unpkg.com/alpinejs" defer></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" defer></script>
<!-- script src="//kit.fontawesome.com/c837098e5b.js" crossorigin="anonymous" defer></script -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<title>{% block title %}Polyphonic{% endblock %}</title>
{% block media %}{% endblock %}
<style>{% block style %}{% endblock %}</style>

View File

@ -36,16 +36,22 @@ Contacts:
{% include 'interface/project_items.html' %}
{% if request.is_admin %}
<div class="box">
<h3 class="subtitle">Admin Details</h3>
<ul>
<li><a href="{{ ensemble_link }}">Ensemble Sharing Link</a></li>
<li><a href="{% url 'project_create' ensemble.pk %}">Add a new project</a></li>
</ul>
<div class="">
<div class="card">
<header class="card header">
<p class="card-header-title">Admin Details</p>
</header>
<div class="card-content">
<ul>
<li><a href="{{ ensemble_link }}">Ensemble Sharing Link</a></li>
<li><a href="{% url 'project_create' ensemble.pk %}">Add a new project</a></li>
</ul>
</div>
</div>
</div>
{% endif %}
<div class="has-text-right is-size-6">
<div>
<a href="{% url 'forget_resource' 'ensemble' ensemble.slug %}">Forget this ensemble</a>
</div>

View File

@ -43,11 +43,8 @@
</div>
</div>
{% empty %}
<div class="hero mx-auto my-4">
<div class="hero-body has-text-centered">
<p class="title">You don't currently have access to any ensembles</p>
<p class="subtitle">Ask your administrator for a link.</p>
</div>
<div class="hero">
You don't currently have access to any ensembles - ask your administrator for a link.
</div>
{% endfor %}
</div>

View File

@ -21,7 +21,7 @@
<p class="menu-label">This Project</p>
<ul class="menu-list">
<li><a role="tab" href="{% url 'project_detail' project=project.id %}">Project Info</a></li>
{% if 'polyphonic.library' in modules %}
{% if 'library' in modules %}
<li><a class="nav-link" href="{% url 'item_list' project=project.pk %}">My Music</a></li>
{% endif %}
{% for page in project.wiki_pages.all %}

View File

@ -29,7 +29,7 @@
</h3>
{% endif %}
<div class="block content">
<div class="block">
{% if project.description %}
<p class="content">{{ project.description|markdown }}</p>
{% else %}
@ -40,36 +40,40 @@
{% if project.owner %}
<div class="block">
{% if project.owner.email %}
The project owner is <strong><a href="mailto:{{ project.owner.email }}">{{ project.owner }}</a></strong>.
The project owner is <a href="mailto:{{ project.owner.email }}">{{ project.owner }}</a>
{% else %}
The project owner is <strong>{{ project.owner }}</strong>.
The project owner is {{ project.owner }}.
{% endif %}
</div>
{% endif %}
</div>
{% if request.is_admin %}
<div class="box">
<h3 class="subtitle">Admin Actions</h3>
<ul>
<li><a href="{{ project_link }}">Project Link</a></li>
</ul>
</div>
{% endif %}
</div>
{% if 'library' in modules %}
<div class="column is-one-third">
{% if 'polyphonic.library' in modules %}
{% include 'library/project_detail.html' %}
</div>
{% endif %}
{% if 'polyphonic.submission' in modules %}
{% if 'submission' in modules %}
<div class="column">
{% include 'submissions/project_detail.html' %}
{% endif %}
</div>
{% endif %}
</div>
{% if request.is_admin %}
<div class="box">
<h3 class="subtitle">Admin Actions</h3>
<ul>
<li><a href="{{ project_link }}">Project Link</a></li>
{% if 'library' in modules %}
<li><a href="{% url 'item_list_manage' project=project.pk %}">Manage items</a></li>
{% endif %}
</ul>
</div>
{% endif %}
<!--
<div>

View File

@ -12,8 +12,7 @@
<p class="card-header-icon" style="color: black;">{{ project.rough_date }}</p>
</header>
</a>
<div class="card-content" style="height: 150px; overflow: hidden">
<div><b>{{ project.event_date|date:"l jS F Y, g:i A" }}</b></div>
<div class="card-content" style="height: 100px; overflow: hidden">
<div class="content">
{{ project.description | markdown }}
</div>
@ -27,8 +26,8 @@
</div>
</div>
{% empty %}
<div class="hero mx-auto my-4">
<div class="hero-body has-text-centered">
<div class="hero">
<div class="hero-body">
<p class="title">No projects currently planned</p>
<p class="subtitle">Go put your feet up!</p>
</div>

View File

@ -5,7 +5,7 @@ register = template.Library()
def basename(value):
return os.path.basename(str(value))
return os.path.basename(value)
register.filter("basename", basename)

View File

@ -292,11 +292,7 @@ class Work(models.Model):
@property
def digital_parts(self):
sections = [
(s.tag, s.name)
for s in Section.objects.filter(doc__work=self.pk)
if ":" not in s.tag
]
sections = [(s.tag, s.name) for s in Section.objects.filter(doc__work=self.pk)]
sections.sort(key=self.orchestration.sorter())
# return [ s[1] for s in sections ]
sections = list(dict(sections).items()) # primitive unique()

View File

@ -1,15 +1,14 @@
from dataclasses import dataclass
from collections import namedtuple
import re
TAG_PREFIXES = {
"mvmt": "Movement",
"ex": "Excerpt",
"sect": "Section",
"name": "Name",
"no": "Number",
"page": "Page",
}
GENERAL = """
mvmt Movement
ex Excerpt
sect Section
pce Piece
no No.
page Page
"""
# taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_MusicTags
# Include any aliases at the top
@ -161,110 +160,71 @@ xyl Xylophone
zith Zither
"""
MUSIC_TAG = re.compile(r"((?P<prefix>\w+):)?(?P<name>.*?)(\-(?P<number>[0-9]+))?")
MUSIC_TAGS = []
TAG_ALIASES = {}
# GENERAL_TAGS = set()
for line in INSTRUMENTS.split("\n"):
parts = line.strip().split(maxsplit=1)
if len(parts) < 2:
continue
name, _, _ = parts[1].partition("(")
MUSIC_TAGS.append((parts[0], name.strip()))
# if i == 0:
# GENERAL_TAGS.add(parts[0])
TAG_ALIASES.setdefault(name, []).append(parts[0])
GENERAL_TAGS = set()
for i, abbreviations in enumerate((GENERAL, INSTRUMENTS)):
for line in abbreviations.split("\n"):
parts = line.strip().split(maxsplit=1)
if len(parts) < 2:
continue
name, _, _ = parts[1].partition("(")
MUSIC_TAGS.append((parts[0], name))
if i == 0:
GENERAL_TAGS.add(parts[0])
MUSIC_NAME_BY_TAG = dict(MUSIC_TAGS)
MUSIC_TAG_BY_NAME = dict(((x[1].lower(), x[0]) for x in MUSIC_TAGS))
def slug(s):
"""
>>> slug("This is a test")
'This_is_a_test'
"""
return str(s).replace(" ", "_")
def deslug(s):
return s.replace("_", " ")
@dataclass
class MusicTag:
name: str
number: int | None = None
prefix: str = ""
class MusicTag(namedtuple("MusicTag", ("name", "variant"), defaults=[None])):
@classmethod
def from_tag(cls, tag):
"""
>>> MusicTag.from_tag('vn-1')
MusicTag(name='Violin', number=1, prefix='')
MusicTag(name='Violin', variant='1')
>>> MusicTag.from_tag('db')
MusicTag(name='Double Bass', number=None, prefix='')
MusicTag(name='Double Bass', variant=None)
>>> MusicTag.from_tag('Jaws Harp')
MusicTag(name='Jaws Harp', number=None, prefix='')
>>> MusicTag.from_tag('mvmt:Largo-2')
MusicTag(name='Largo', number=2, prefix='mvmt')
>>> MusicTag.from_tag('name:A2')
MusicTag(name='A2', number=None, prefix='name')
>>> MusicTag.from_tag('name:Ode_to_Joy')
MusicTag(name='Ode to Joy', number=None, prefix='name')
>>> MusicTag.from_tag('no:-2')
MusicTag(name='', number=2, prefix='no')
MusicTag(name='Jaws Harp', variant=None)
>>> MusicTag.from_tag('mvmt-2')
MusicTag(name='Movement', variant='2')
>>> MusicTag.from_tag('pce-A2')
MusicTag(name='Piece', variant='A2')
"""
abbr, _, variant = tag.partition("-")
name = MUSIC_NAME_BY_TAG.get(abbr.lower(), abbr)
match = MUSIC_TAG.fullmatch(tag)
if variant:
return cls(name, variant)
return cls(name, None)
if match is None:
raise ValueError("Not a valid tag")
result = match.groupdict()
if result["prefix"] is None:
result["name"] = MUSIC_NAME_BY_TAG.get(
result["name"].lower(), deslug(result["name"])
)
result["prefix"] = ""
else:
result["name"] = deslug(result["name"])
result["number"] = int(result["number"]) if result["number"] else None
return cls(**result)
@property
def tag(self):
lc = self.name.lower()
return MUSIC_TAG_BY_NAME.get(lc, lc)
@property
def is_general(self):
"""
>>> MusicTag('A3', prefix="name").is_general
>>> MusicTag('Piece', 'A3').is_general
True
>>> MusicTag('Violin', 2).is_general
False
"""
return self.prefix in TAG_PREFIXES
return self.tag in GENERAL_TAGS
@property
def tag(self):
def abbreviate(self):
"""
>>> MusicTag('Violin', 1).tag
>>> MusicTag('Violin', 1).abbreviate()
'vn-1'
>>> MusicTag('Double Bass').tag
>>> MusicTag('Double Bass').abbreviate()
'db'
>>> MusicTag('left', prefix="page:").tag
'page:left'
>>> MusicTag("Ode to Joy", prefix="name:").tag
'name:Ode_to_Joy'
>>> MusicTag('Unknown Instrument').tag
'Unknown_Instrument'
"""
parts = [self.prefix]
parts.append(MUSIC_TAG_BY_NAME.get(self.name.lower(), slug(self.name)))
if self.number:
parts.extend(["-", str(self.number)])
return "".join(parts)
tag = MUSIC_TAG_BY_NAME.get(self.name.lower())
if self.variant:
tag = f"{tag}-{self.variant}"
return tag
def __str__(self):
"""
@ -272,17 +232,15 @@ class MusicTag:
'Violin 1'
>>> str(MusicTag('Double Bass'))
'Double Bass'
>>> str(MusicTag('Unknown Instrument'))
'Unknown Instrument'
"""
if self.number:
return f"{self.name} {self.number}"
if self.variant:
return f"{self.name} {self.variant}"
return self.name
PATTERNS = [
re.compile(r"(?P<inst>[A-Za-z]+)[_\- ]*(?P<number>\d+)"),
re.compile(r"(?P<number>\d+)(st|nd|rd|th)[_\- ]*(?P<inst>[A-Za-z]+)"),
re.compile(r"(?P<inst>[A-Za-z]+)[_\- ]*(?P<ord>\d+)"),
re.compile(r"(?P<ord>\d+)(st|nd|rd|th)[_\- ]*(?P<inst>[A-Za-z]+)"),
re.compile(r"(?P<inst>[A-Za-z]+)()"),
]
@ -290,33 +248,34 @@ PATTERNS = [
def auto_tag(filename):
"""
>>> auto_tag('Ode to Joy - Violin 1.pdf').tag
'vn-1'
>>> auto_tag('Ode to Joy_Cello.pdf').tag
'vc'
>>> auto_tag('Ode to Joy violin - 1.pdf').tag
'vn-1'
>>> auto_tag('Ode to Joy - vla.pdf').tag
'va'
>>> auto_tag('Ode to Joy - fl-2 (piccolo).pdf').tag
'fl-2'
>>> auto_tag('1st Violin - Ode to Joy.pdf').tag
'vn-1'
>>> auto_tag('Ode to Joy - 2nd Violin.pdf').tag
'vn-2'
>>> auto_tag('Ode to Joy - Violin 1.pdf')
MusicTag(name='Violin', variant=1)
>>> auto_tag('Ode to Joy_Cello.pdf')
MusicTag(name='Cello', variant=None)
>>> auto_tag('Ode to Joy violin - 1.pdf')
MusicTag(name='Violin', variant=1)
>>> auto_tag('Ode to Joy - vla.pdf')
MusicTag(name='Viola', variant=None)
>>> auto_tag('Ode to Joy - fl-2 (piccolo).pdf')
MusicTag(name='Flute', variant=2)
>>> auto_tag('1st Violin - Ode to Joy.pdf')
MusicTag(name='Violin', variant=1)
>>> auto_tag('Ode to Joy - 2nd Violin.pdf')
MusicTag(name='Violin', variant=2)
"""
for pattern in PATTERNS:
for m in pattern.finditer(filename):
inst = m["inst"].lower()
try:
number = int(m["number"])
ordinal = int(m["ord"])
except IndexError:
number = None
ordinal = None
if inst in MUSIC_TAG_BY_NAME:
return MusicTag(inst.title(), number)
return MusicTag(inst.title(), ordinal)
if inst in MUSIC_NAME_BY_TAG:
return MusicTag(MUSIC_NAME_BY_TAG[inst], number)
return MusicTag(MUSIC_NAME_BY_TAG[inst], ordinal)
if __name__ == "__main__":

View File

@ -80,7 +80,6 @@
.tag-name {
flex: 1;
text-align: right;
cursor: pointer;
}
.tag-handle-resize {
position: absolute;
@ -141,7 +140,7 @@
</ul>
<h3>Actions</h3>
<div>
<a class="button is-small is-primary" onclick="showTagModal(createNewTag)">New Tag</a>
<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>
@ -164,19 +163,18 @@
</div>
</div>
<div class="modal" id="tag-modal">
<div class="modal-background" onclick="closeTagModal()"></div>
<div class="modal" id="add-modal">
<div class="modal-background" onclick="closeAddModal()"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Tag Editor</p>
<button class="delete" aria-label="close" onclick="closeTagModal()"></button>
<p class="modal-card-title">Add Tag</p>
<button class="delete" aria-label="close" onclick="closeAddModal()"></button>
</header>
<section class="modal-card-body">
<form>
<div class="field has-addons is-justify-content-center">
<span class="control">
<span class="select">
<select onChange="onChangeTagType()" id="tag-prefix">
<select onChange="changeTagType()" id="tag-type-selection">
<option value="inst">Instrument</option>
<option value="mvmt">Movement</option>
<option value="sect">Section</option>
@ -185,34 +183,32 @@
</select>
</span>
</span>
<span class="control tag-option enable-page">
<span class="select tag-name">
<select id="tag-name-page">
<option value="left">Left</option>
<option value="right">Right</option>
<option value="blank">Add Blank</option>
<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 tag-option enable-sect enable-mvmt">
<input type="text" class="input tag-name" id="tag-name" />
<span class="control add-tag-option enable-sect">
<input type="text" class="input" id="add-tag-name"/>
</span>
<span class="control tag-option enable-inst">
<input type="text" class="input tag-name" list="instrument-list" id="tag-name-inst"/>
<span class="control add-tag-option enable-inst">
<input type="text" class="input" list="instrument-list" id="add-tag-inst"/>
<datalist id="instrument-list">
{% for inst in json_data.instrumentNames %}
{% for inst in json_data.instruments.values %}
<option value="{{inst}}"/>
{% endfor %}
</datalist>
</span>
<span class="control tag-option enable-inst enable-mvmt enable-no">
<input type="number" class="input tag-number" min="1" size="3" id="tag-number"/>
<span class="control add-tag-option enable-inst enable-mvmt enable-no">
<input type="number" class="input" min="1" size="3" id="add-tag-ordinal"/>
</span>
<span class="control">
<button type="submit" class="button is-primary" onclick="event.preventDefault(); confirmTagModal(); ">Save</button>
<button class="button is-primary" onclick="createNewTag();">Add</button>
</span>
</div>
</form>
</section>
</div>
</div>
@ -248,7 +244,6 @@
let data = JSON.parse(document.getElementById('data').textContent);
let tagArea = document.getElementById('tag-area');
var dirty = false;
var tagModalCallback = null;
//document.getElementById('tag-list').onclick = (e) => setTag(e.target.dataset.tag);
@ -410,27 +405,15 @@
dirty = false;
});
function showTagModal(callback, tag) {
document.getElementById("tag-modal").classList.add("is-active");
tagModalCallback = callback;
if(tag) {
const parts = parseTag(tag);
console.log(parts);
document.querySelector("#tag-prefix").value = parts.prefix;
onChangeTagType();
document.querySelectorAll(".tag-name").forEach(el => el.value = parts.name);
document.querySelector(".tag-number").value = parts.number;
} else {
onChangeTagType();
}
function showAddModal() {
document.getElementById("add-modal").classList.add("is-active");
changeTagType();
}
function closeTagModal() {
document.getElementById('tag-modal').classList.remove('is-active');
function closeAddModal() {
document.getElementById('add-modal').classList.remove('is-active');
}
function addNumberedInstrument(tag, e) {
let modal = document.getElementById('add-modal');
@ -441,48 +424,55 @@
modal.classList.add('is-active');
}
function onChangeTagType() {
const tag_prefix = document.querySelector("#tag-prefix");
const selected = tag_prefix.value;
document.querySelectorAll(".tag-option").forEach((x) => {x.classList.add("is-hidden")});
document.querySelectorAll(".tag-option input").forEach((x) => {x.value=""});
function changeTagType() {
const tag_select = document.querySelector("#tag-type-selection");
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_prefix.focus();
tag_select.focus();
}
function confirmTagModal() {
const selected = document.querySelector("#tag-prefix").value;
let name = document.querySelector("#tag-name").value;
const n = document.querySelector("#tag-number").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":
if (!n) throw new Error("Missing number");
case "page":
name = document.querySelector("#tag-name-page").value;
return selected + "-" + n;
case "sect":
return "sect-" + document.querySelector("#add-tag-name").value;
case "inst":
name = document.querySelector("#tag-name-inst").value;
const inst_name = document.querySelector("#add-tag-inst").value;
var tag = null;
for (let key in data.instruments) {
if (data.instruments[key] == name) {
name = key;
if (data.instruments[key] == inst_name) {
tag = key;
break;
}
}
if (!tag) {
tag = inst_name;
}
if (n) {
tag += "-" + n;
}
return tag;
case "page":
return "page-" + document.querySelector("#add-tag-page").value;
}
name = name.replace(" ", "_");
const prefix = (selected == "inst") ? "" : selected + ":";
const tag = (n) ? prefix + name + "-" + n : prefix + name;
closeTagModal();
tagModalCallback(tag);
}
function createNewTag(tag) {
function createNewTag() {
const tag = generateNewTag()
addTag(tag, pageNum, pageNum);
dirty = true;
closeAddModal();
}
function assignTag(tag, el) {
@ -490,16 +480,6 @@
//el.remove();
}
function editTag(e) {
console.log("EDIT", e.target);
showTagModal((tag) => {
console.log("Updating to " + tag);
e.target.innerHTML = get_tag_name(tag);
e.target.parentNode.dataset["tag"] = tag;
dirty = true;
}, e.target.parentNode.dataset["tag"]);
}
function addTag(tag, start, end) {
@ -509,15 +489,13 @@
const tagName = el.querySelector(".tag-name");
tagName.innerHTML=get_tag_name(tag);
tagName.addEventListener('dblclick', editTag);
//el.className = 'grid-tag';
el.dataset.start = start;
el.dataset.end = end;
el.dataset.tag = tag;
const parts = parseTag(tag)
el.classList.add("tag-type-" + parts.prefix);
const parts = tag.split("-")
el.classList.add("tag-type-" + parts[0]);
const handle = el.querySelector('.tag-handle');
@ -696,7 +674,7 @@
onPrevPage();
break;
case "n":
showTagModal(createNewTag);
showAddModal();
break;
default:
return;
@ -734,43 +712,13 @@
dirty = false;
}
function parseTag(tag) {
const result = {
prefix: "inst",
name: "",
number: "",
};
let parts = tag.split(":");
if(parts.length == 2) {
result.prefix = parts[0];
tag = parts[1];
}
parts = tag.split('-');
if(parts.length == 2) {
result.number = parts[1];
}
result.name = parts[0];
if(result.prefix == "inst") {
result.name = data.instruments[result.name] || result.name;
}
result.name = result.name.replace("_", " ");
return result;
}
function get_tag_name(s) {
tag = parseTag(s);
console.log(tag);
let prefix = (tag.prefix == "inst") ? "" : data.prefixes[tag.prefix] + " ";
return (tag.number) ? prefix + tag.name + " " + tag.number : prefix + tag.name;
let parts = s.split('-');
let instrument = data.instruments[parts[0]] || parts[0];
if (parts.length == 2) {
return instrument + " " + parts[1];
}
return instrument;
}
function checkSaved(e) {

View File

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

View File

@ -2,7 +2,7 @@
{% load polyphonic %}
{% block admin %}
<a href="{% url 'project_work_list' project.pk %}" class="button is-link">
<a href="{% url 'item_list_append' project.pk %}" class="button is-link">
{% icon "add_circle" %}
<span>Add</span>
</a>
@ -13,48 +13,37 @@
{% endblock %}
{% block page %}
<div class="columns">
<div class="column card">
<table style="max-width: 600px; margin: 10pt auto;" class="table is-striped is-narrow">
<thead>
<table style="max-width: 600px; margin: 10pt auto;" class="zebra">
<thead>
<tr>
<th>Item</th>
<th>Time</th>
<th/>
<th>Item</th>
<th>Time</th>
<th/>
</tr>
</thead>
<tbody id="work-list">
</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 is-clickable" title="Remove" onClick="removeItem({{ item.pk }})">
{% icon "delete" %}
</span>
<span class="clickable drag-handle cursor-drag" title="Reorder">
{% icon "reorder" %}
</span>
</td>
<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>
</tr>
{% empty %}
<tr><td colspan="2">No items</td></tr>
<tr><td colspan="2">No items</td></tr>
{% endfor %}
</tbody>
</table>
<div>
</div>
{% endblock %}
{% block style %}
{{ block.super }}
<style>
tr.dragging { opacity: 0.4; }
.drag-handle { cursor: move; }
</style>
</tbody>
</table>
{% endblock %}
{% block scripts %}
@ -62,82 +51,36 @@
let workList = document.getElementById('work-list');
var dirty = false;
var dragSrc = null;
var dragSrcNext = null;
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) {
function moveItem(pk, dir) {
let items = Array.prototype.slice.call(workList.children, 0);
var i=0;
pk = "" + pk;
for (let i = 0; i < items.length; i++) {
if (items[i].dataset.pk === pk) {
items[i].dataset.order = -1;
items[i].style = "display: none";
break;
}
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;
}
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.setDragImage(e.target, 0, 0);
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() {
let items = Array.prototype.slice.call(workList.children, 0);
@ -155,7 +98,7 @@ function save() {
}).then((response) => {
if (response.ok) {
dirty=false;
window.location = "{% url 'project_detail' project=project.pk %}";
window.location = "{% url 'item_list' project=project.pk %}";
} else {
alert("Failed: " + response.statusText)
}

View File

@ -1,9 +1,6 @@
<div class="panel">
<p class="panel-heading">
Items
{% if request.is_admin %}
<a href="{% url 'item_list_manage' project=project.pk %}" class="button is-small is-pulled-right">Edit Items</a>
{% endif %}
</p>
{% for item in project.items.all %}
@ -23,7 +20,7 @@
<div class="modal-background" onclick="closeDownloadModal()"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Available Parts</p>
<p class="modal-card-title">Parts</p>
<button class="delete" aria-label="close" onclick="closeDownloadModal()"></button>
</header>
<section class="modal-card-body" id="download-target">
@ -50,4 +47,4 @@ function closeDownloadModal() {
document.getElementById("download-modal").classList.remove('is-active');
}
</script>
</script>

View File

@ -9,12 +9,6 @@
<span>Add a work</span>
</a>
{% endif %}
{% if project %}
<a href="{% url 'item_list_manage' project.pk %}" class="button">
{% icon "add_notes" %}
<span>Back</span>
</a>
{% endif %}
{% endblock %}
{% block page %}
@ -47,8 +41,7 @@
<th>Work <span class="icon-text"><a href="?start={{ start }}&sort=name">{{ "list_arrow"|icon }}</a></span></th>
<th>Composer <span class="icon-text"><a href="?start={{ start }}&sort=composer">{{ "list_arrow"|icon }}</a></span></th>
<th class="is-hidden-mobile">Edition</th>
{% if not collection and not project %}<th class="is-hidden-touch">Collection</th>{% endif %}
{% if project %}<th/>{% endif %}
{% if not collection %}<th class="is-hidden-touch">Collection</th>{% endif %}
</tr>
</thead>
<tbody>
@ -59,14 +52,7 @@
</td>
<td title="{{ work.composer }}">{{ work.composer|truncatewords:3 }}</td>
<td class="is-hidden-mobile" title="{{ work.edition }}">{{ work.edition|truncatewords:2 }}</td>
{% if not collection and not project %}<td class="is-hidden-touch">{{ work.collection.name }}</td>{% endif %}
{% if project %}
<td>
<button data-work="{{ work.pk }}" onclick="addToProject({{ work.pk }})" class="button is-primary is-small">
{% if work.pk in current %} Added {% else %} Add {% endif %}
</button>
</td>
{% endif %}
{% if not collection %}<td class="is-hidden-touch">{{ work.collection.name }}</td>{% endif %}
</tr>
{% empty %}
<tr><td colspan="4">No works found</td></tr>
@ -114,24 +100,3 @@ Query="{{ meta.query }}"
</footer>
{% endblock %}
{% block scripts %}
<script>
async function addToProject(work) {
console.log("ADD", work);
const response = await fetch("", {
method: "POST",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: new URLSearchParams({ work, csrfmiddlewaretoken: "{{ csrf_token }}" }),
});
if (response.status === 201) {
document.querySelector(`[data-work='${work}']`).innerHTML = "Added";
} else {
alert("Failed to add work\nCheck console for details");
console.error(response);
}
console.log(response);
}
</script>
{% endblock %}

View File

@ -1,10 +1,27 @@
<h3 class="subtitle">{{ work.name }}</h3>
<div class="block tags">
{% for tag, name in work.digital_parts %}
<a class="tag is-info" href="{% url 'work_download' work.collection_id work.pk %}?tag={{ tag }}" target="_blank">{{ name }}</a>
<a class="tag is-info" href="{% url 'work_download' work.collection_id work.pk %}?tag={{ tag }}" target="polyphonic_parts">{{ name }}</a>
{% endfor %}
</div>
<p class="has-text-right">
<a href="{% url 'work_detail' work.collection_id work.pk %}">Full details for {{ work.name }}</a>
</p>
<!--
<h3 class="subtitle">Files</h3>
<table class="table is-narrow is-fullwidth">
<tbody>
{% for doc in work.pdfs %}
<tr>
<td><a href="{{ doc.upload.url }}">{{ doc.filename }}</a></td>
</tr>
{% empty %}
<tr>
<td>There are no files available for this work</td>
</tr>
{% endfor %}
</tbody>
</table>
-->
<p>
<a href="{% url 'work_detail' work.collection_id work.pk %}">More details...</a>
</p>

View File

@ -1,6 +1,6 @@
from doctest import DocTestSuite
from polyphonic.library import music_tags
from library import music_tags
def load_tests(loader, tests, ignore):

View File

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

View File

@ -1,5 +1,5 @@
from django.shortcuts import get_object_or_404, redirect, resolve_url
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest
from django.views.generic import TemplateView
from django.views.generic.detail import DetailView, SingleObjectMixin, View
from django.views.generic.list import ListView
@ -11,7 +11,6 @@ from django.utils.timezone import now
from django.template.loader import render_to_string
from django.core.exceptions import SuspiciousOperation
from django.core.paginator import Paginator
from django.http import Http404, HttpResponseRedirect
import json
@ -21,13 +20,7 @@ import string
from polyphonic.interface.views import ProjectMixin, AuthorizedResourceMixin
from polyphonic.interface.utils import signed_url
from polyphonic.library.models import Collection, Work, Document, Section
from polyphonic.library.music_tags import (
MUSIC_TAGS,
MUSIC_NAME_BY_TAG,
TAG_PREFIXES,
TAG_ALIASES,
MusicTag,
)
from polyphonic.library.music_tags import MUSIC_TAGS, MusicTag
from polyphonic.library import forms, models
from polyphonic.library.pdf_utils import extract_pages, extract_and_concat
from polyphonic.library.indexer import index_works, model_search
@ -92,7 +85,7 @@ class ProjectItemListView(ProjectMixin, ListView):
def get_context_data(self, **kwargs):
data = super(ProjectItemListView, self).get_context_data(**kwargs)
data["instruments"] = TAG_ALIASES
data["instruments"] = MUSIC_TAGS
data["instrument"] = self.request.session.get("instrument", "Score")
data["part"] = self.request.session.get("part", "0")
data["running_time"] = self.get_queryset().aggregate(Sum("work__running_time"))[
@ -301,39 +294,6 @@ class CollectionWorkListView(WorkListView):
return data
class ProjectWorkListView(WorkListView, ProjectMixin):
http_method_names = ["get", "post"]
def is_authorized(self):
return ProjectMixin.is_authorized(self)
def post(self, request, project):
work = request.POST.get("work")
print("WORK", work)
self.project.items.create(
work_id=work, checkout=now(), approved_by=request.user, order=100
)
return HttpResponse(status=201)
def get_works(self):
collections = self.get_collections() or []
print(collections)
return Work.objects.filter(collection_id__in=collections)
def get_collections(self):
return self.project.ensemble.allowed_collections.values_list(
"collection_id", flat=True
)
def get_context_data(self, *args, **kwargs):
data = super(ProjectWorkListView, self).get_context_data(*args, **kwargs)
data["title"] = f"Library for {self.project.ensemble}"
data["current"] = set(self.project.items.values_list("work_id", flat=True))
return data
class WorkAddView(CollectionMixin, FormView):
template_name = "interface/default_form.html"
form_class = forms.WorkCreateForm
@ -656,12 +616,7 @@ class DocumentAnnotateView(DocumentMixin, DetailView):
("sect", "success"),
]
data["json_data"] = {
"pageTags": pages,
"instruments": MUSIC_NAME_BY_TAG,
"instrumentNames": list({x[1] for x in MUSIC_TAGS}),
"prefixes": TAG_PREFIXES,
}
data["json_data"] = {"pageTags": pages, "instruments": dict(MUSIC_TAGS)}
return data