Compare commits
14
Commits
ef981e06e8
...
0.8.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a22264d3f | ||
|
|
fefa0a28b6 | ||
|
|
0e69cdeca4 | ||
|
|
e797876c16 | ||
|
|
1301d19c08 | ||
|
|
ca3effcad1 | ||
|
|
5468f6d3e7 | ||
|
|
e46d8145a7 | ||
|
|
3444cdbc59 | ||
|
|
6e77474d15 | ||
|
|
72bad08a61 | ||
|
|
b217fbea5e | ||
|
|
cec64ecd2f | ||
|
|
d30005d5b6 |
@@ -5,7 +5,10 @@ __pycache__
|
||||
credentials.json
|
||||
credentials
|
||||
local_settings.py
|
||||
local.mk
|
||||
.coverage
|
||||
.lint
|
||||
.deploy
|
||||
Session.vim
|
||||
poetry.lock
|
||||
/env
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
FROM alpine:latest
|
||||
|
||||
ENV TARGET=/opt/polyphonic
|
||||
ENV RELEASE=polyphonic-0.8.4-py3-none-any.whl
|
||||
ENV RELEASE=polyphonic-0.8.4-py3-none-any.whl
|
||||
#ENV RELEASE=git+https://gitea.tfconsulting.com.au/projects/polyphonic.git
|
||||
|
||||
RUN apk add --no-cache python3 py3-pip git ghostscript sqlite
|
||||
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
PYTHON=env/bin/python
|
||||
DROPZONE=5.7.0
|
||||
|
||||
test:
|
||||
VERSION=0.8.4
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=polyphonic.config.settings.dev
|
||||
|
||||
-include local.mk
|
||||
|
||||
test: .coverage
|
||||
|
||||
check: .lint
|
||||
|
||||
pre-commit: check test
|
||||
|
||||
.coverage: polyphonic
|
||||
poetry run coverage run --include "polyphonic/*" --omit "*/migrations/*" polyphonic/manage.py test polyphonic
|
||||
poetry run coverage html
|
||||
poetry run coverage report
|
||||
|
||||
check:
|
||||
.lint: polyphonic
|
||||
poetry run ruff check polyphonic
|
||||
poetry run ruff format --check polyphonic
|
||||
touch $@
|
||||
|
||||
pre-commit: check test
|
||||
|
||||
build:
|
||||
build: dist/polyphonic-${VERSION}-py3-none-any.whl
|
||||
|
||||
dist/polyphonic-${VERSION}-py3-none-any.whl: polyphonic
|
||||
poetry build
|
||||
|
||||
dev-setup:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .base import * # noqa
|
||||
from os import environ
|
||||
|
||||
DEBUG = True
|
||||
|
||||
@@ -8,3 +9,20 @@ SECRET_KEY = "DO NOT USE IN PRODUCTION"
|
||||
INSTALLED_APPS.append("debug_toolbar") # noqa
|
||||
MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa
|
||||
INTERNAL_IPS = ["127.0.0.1"]
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": environ.get("DEBUG_LEVEL", "WARNING"),
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"polyphonic": {
|
||||
"handlers": ["console"],
|
||||
"level": environ.get("DEBUG_LEVEL", "WARNING"),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
.menu-label {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/*
|
||||
.button.is-primary, .button.is-primary:hover {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
*/
|
||||
|
||||
A.admin-link:after {
|
||||
content: "*";
|
||||
@@ -60,4 +61,4 @@ TEXTAREA.input {
|
||||
padding: 10px 10px;
|
||||
margin-top: 30px;
|
||||
border-top-left-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<!-- 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 'interface/icon.png' %}" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||
<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>
|
||||
<!-- 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>
|
||||
@@ -23,7 +25,7 @@
|
||||
<nav class="navbar" role="navigation">
|
||||
<div class="navbar-brand has-text-primary">
|
||||
<a class="navbar-item" href="/">
|
||||
<span class="icon fancy is-size-2 is-size-4-mobile mx-4"><i class="fas fa-random"></i></span>
|
||||
<span class="icon fancy mx-4"><span class="material-symbols-outlined is-size-1 is-size-3-mobile">groups</span></span>
|
||||
<span class="fancy is-size-2 is-size-4-mobile">Polyphonic</span>
|
||||
</a>
|
||||
<span class="navbar-item is-hidden-mobile fancy is-size-5">Musical Ensemble Manager</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% crispy_field field 'class' 'file-input'%}
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span class="material-symbols-outlined">file_upload</span>
|
||||
</span>
|
||||
<span class="file-label">
|
||||
Choose a file…
|
||||
@@ -25,4 +25,4 @@
|
||||
fileName.textContent = fileInput.files[0].name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load md2 %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'project_create' object.slug %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
{{ "add_notes"|icon }}
|
||||
<span>Add project</span>
|
||||
</a>
|
||||
{% if inactive %}
|
||||
<a href="?" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-archive"></i></span>
|
||||
{{ "preview_off"|icon }}
|
||||
<span>Hide old</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="?inactive" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-archive"></i></span>
|
||||
{{ "preview"|icon }}
|
||||
<span>Show all</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -54,4 +55,4 @@ Contacts:
|
||||
<a href="{% url 'forget_resource' 'ensemble' ensemble.slug %}">Forget this ensemble</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% comment %}
|
||||
<div class="admin-tools is-pulled-right">
|
||||
<a class="button is-link" href="{% url 'register' %}">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
{% icon "add_file" %}
|
||||
<span>Register another</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -25,10 +25,13 @@
|
||||
<img src="https://www.gravatar.com/avatar/{{ ensemble.email }}?d=mp" alt="Placeholder image">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content" style="min-height: 60px">
|
||||
<div class="media-content" style="min-height: 100px">
|
||||
<a href="{% url 'ensemble_detail' ensemble.slug %}">
|
||||
<p class="title is-4">{{ ensemble.name }}</p>
|
||||
</a>
|
||||
<div class="mt-3">
|
||||
{{ ensemble.details|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,4 +48,4 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'wiki_create' project=project.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-file"></i></span>
|
||||
<span>Add Page</span>
|
||||
{{ "add_notes"|icon }}
|
||||
<span>Add Page</span>
|
||||
</a>
|
||||
<a href="{% url 'project_edit' project=project.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-edit"></i></span>
|
||||
{{ "edit"|icon }}
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -33,4 +33,4 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load md2 %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
<a class="button is-link" href="{% url 'resource_create' project=project.pk %}">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
{% icon "add_notes" %}
|
||||
<span>Add new</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -27,11 +28,11 @@
|
||||
|
||||
<div class="card-header-icon">
|
||||
{% if request.is_admin %}
|
||||
<a href="{% url 'resource_upload' project=project.pk pk=resource.pk %}" class="icon" title="Upload">
|
||||
<i class="fas fa-upload"></i>
|
||||
<a href="{% url 'resource_upload' project=project.pk pk=resource.pk %}" title="Upload">
|
||||
{% icon "upload_file" %}
|
||||
</a>
|
||||
<a href="{% url 'resource_edit' project=project.pk pk=resource.pk %}" class="icon" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
<a href="{% url 'resource_edit' project=project.pk pk=resource.pk %}" title="Edit">
|
||||
{% icon "edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'wiki_edit' project=project.pk pk=wikipage.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-edit"></i></span>
|
||||
<span>Edit</span>
|
||||
{{ "edit"|icon }}
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,4 +13,4 @@
|
||||
<div class="box content wiki-page">
|
||||
{{ wiki_html|safe }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
from django import template
|
||||
from django.utils import timesince
|
||||
from django.utils.html import format_html
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter("icon", is_safe=True)
|
||||
def material_icon(value):
|
||||
return f'<span class="icon"><span class="material-symbols-outlined">{value}</span></span>'
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def icon(name, element="span", classes=[]):
|
||||
classes = ["icon"] + classes
|
||||
return format_html(
|
||||
'<{} class="{}"><span class="material-symbols-outlined">{}</span></{}>',
|
||||
element,
|
||||
" ".join(classes),
|
||||
name,
|
||||
element,
|
||||
)
|
||||
|
||||
|
||||
def roughtimesince(value):
|
||||
return timesince.timesince(value, depth=1)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from polyphonic.library.models import Collection, Work, WorkMeta, Document
|
||||
from byostorage.models import UserStorage
|
||||
|
||||
import logging
|
||||
|
||||
@@ -6,21 +7,26 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sync_work(work: Work):
|
||||
logger.info("Syncing '%s'", work.name)
|
||||
folder_id = work.meta_info.get(name="folderid").value
|
||||
try:
|
||||
folder_id = work.meta_info.get(name="folderid").value
|
||||
except WorkMeta.DoesNotExist as err:
|
||||
raise IndexError("Work not currently linked to a gdrive folder") from err
|
||||
|
||||
storage = work.collection.storage.instance()
|
||||
prefix = work.collection.storage.name
|
||||
_, files = storage.listdir(folder_id)
|
||||
logger.info("Syncing '%s' from %r", work.name, folder_id)
|
||||
|
||||
storage = UserStorage.objects.get(name="gdrive").instance()
|
||||
|
||||
existing = set(
|
||||
[
|
||||
storage.parse_id(x.partition(":")[2])
|
||||
storage.parse_resource(x.partition(":")[2]).id
|
||||
for x in work.docs.values_list("upload", flat=True)
|
||||
]
|
||||
)
|
||||
logger.debug("%d existing documents", len(existing))
|
||||
|
||||
_, files = storage.listdir(folder_id)
|
||||
logger.debug("Remote files: %r", files)
|
||||
|
||||
for file in files:
|
||||
if file.id in existing:
|
||||
logger.debug("%30s: Skipping existing (%s)", file.name, file.id)
|
||||
@@ -32,21 +38,26 @@ def sync_work(work: Work):
|
||||
continue
|
||||
|
||||
logger.info("%40s: Adding", file.name)
|
||||
doc = work.docs.create(upload=f"{prefix}:{file}", doctype=Document.DOCTYPE_PDF)
|
||||
doc = work.docs.create(upload=f"gdrive:{file}", doctype=Document.DOCTYPE_PDF)
|
||||
doc.auto_tag()
|
||||
|
||||
for uri in existing:
|
||||
logger.warning("Local entry not in folder: %s", uri)
|
||||
|
||||
|
||||
def sync_partial_collection(collection: Collection, sync_existing: bool = True):
|
||||
|
||||
works = Work.objects.filter(collection=collection, meta_info__name="folderid")
|
||||
|
||||
for work in works:
|
||||
sync_work(work)
|
||||
|
||||
|
||||
def sync_collection(collection: Collection, sync_existing: bool = False):
|
||||
logger.info("Syncing '%s'", collection)
|
||||
|
||||
if not collection.storage.storage.endswith("GDriveLinkStorage"):
|
||||
raise RuntimeError("Not a gdrive storage")
|
||||
|
||||
if not collection.prefix:
|
||||
raise KeyError("Prefix must store folder id")
|
||||
return sync_partial_collection(collection, sync_existing)
|
||||
|
||||
existing = dict(
|
||||
WorkMeta.objects.filter(
|
||||
@@ -54,10 +65,14 @@ def sync_collection(collection: Collection, sync_existing: bool = False):
|
||||
).values_list("value", "work_id")
|
||||
)
|
||||
|
||||
folder = collection.prefix
|
||||
storage = collection.storage.instance()
|
||||
folders, _ = storage.listdir(collection.prefix)
|
||||
folders, _ = storage.listdir(folder)
|
||||
|
||||
for folder in folders:
|
||||
if folder[0] == "_":
|
||||
continue
|
||||
|
||||
if folder.id in existing:
|
||||
if sync_existing:
|
||||
logger.info("%40s: Syncing (%s)", folder.name, folder.id[:12])
|
||||
|
||||
@@ -8,19 +8,26 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SHARED_FOLDER = re.compile(r"https://drive.google.com/drive/folders/(\w+)")
|
||||
SHARED_FILE = re.compile(r"https://drive.google.com/file/d/([\w\-]+)")
|
||||
SHARED_FOLDER = re.compile(
|
||||
r"https://drive.google.com/drive[u0-9\/]+folders/([\w\-]+)(\?resourcekey=([\w\-]+))?"
|
||||
)
|
||||
SHARED_FILE = re.compile(
|
||||
r"https://drive.google.com/file/d/([\w\-]+)(\?resourcekey=([\w\-]+))?"
|
||||
)
|
||||
|
||||
FILES_API = "https://www.googleapis.com/drive/v3/files"
|
||||
|
||||
|
||||
class DriveObject(namedtuple("DriveObject", ("id", "name"))):
|
||||
class DriveObject(namedtuple("DriveObject", ("id", "key", "name"))):
|
||||
@classmethod
|
||||
def from_string(cls, s):
|
||||
return cls(*s.split("/", 1))
|
||||
def from_string(cls, s: str):
|
||||
resource, _, name = s.partition("/")
|
||||
id, _, key = resource.partition("#")
|
||||
return cls(id, key, name)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.id}/{self.name}"
|
||||
resource = f"{self.id}#{self.key}" if self.key else self.id
|
||||
return f"{resource}/{self.name}"
|
||||
|
||||
|
||||
class GDriveLinkStorage(Storage):
|
||||
@@ -30,57 +37,68 @@ class GDriveLinkStorage(Storage):
|
||||
self.api_key = api_key
|
||||
super().__init__()
|
||||
|
||||
def parse_id(self, name):
|
||||
parts = name.split("/")
|
||||
return parts[0]
|
||||
def parse_resource(self, name) -> DriveObject:
|
||||
return DriveObject.from_string(name)
|
||||
|
||||
def extract_id(self, url, *patterns):
|
||||
logger.debug("EXTRACT_ID: %r", url)
|
||||
def extract_resource(self, url, *patterns) -> DriveObject:
|
||||
logger.debug("EXTRACT_RESOURCE: %r", url)
|
||||
for pattern in patterns:
|
||||
match = pattern.match(url)
|
||||
if match:
|
||||
return match.groups()[0]
|
||||
groups = match.groups()
|
||||
logger.debug(groups)
|
||||
if len(groups) == 3:
|
||||
return DriveObject(groups[0], groups[2], "")
|
||||
return DriveObject(groups[0], None, "")
|
||||
raise FileNotFoundError(f"Not a valid url: {url}")
|
||||
|
||||
def get_json(self, url):
|
||||
logger.debug("GET_JSON: %s", url)
|
||||
response = requests.get(url)
|
||||
def get_json(self, url, resource: DriveObject):
|
||||
headers = {}
|
||||
if resource.key:
|
||||
headers["X-Goog-Drive-Resource-Keys"] = f"{resource.id}/{resource.key}"
|
||||
|
||||
logger.debug("GET_JSON: %s %r", url, headers)
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
logger.debug("Data: %r", data)
|
||||
return data
|
||||
|
||||
def listdir(self, path) -> tuple[list[str], list[str]]:
|
||||
|
||||
# used to test for valid connection parameters - should do something to validate API key here
|
||||
logger.debug("listdir: %s", path)
|
||||
if path == "":
|
||||
return [], []
|
||||
|
||||
logger.debug("LISTDIR: %s", path)
|
||||
folder_id = self.parse_id(path)
|
||||
url = f"{FILES_API}?q='{folder_id}'+in+parents&key={self.api_key}"
|
||||
data = self.get_json(url)
|
||||
folder = self.parse_resource(path)
|
||||
url = f"{FILES_API}?q='{folder.id}'+in+parents&key={self.api_key}"
|
||||
data = self.get_json(url, folder)
|
||||
files = []
|
||||
folders = []
|
||||
for x in data["files"]:
|
||||
if x["mimeType"] == "application/vnd.google-apps.folder":
|
||||
# folders.append(f"{x['id']}/{x['name']}")
|
||||
folders.append(DriveObject(x["id"], x["name"]))
|
||||
folders.append(DriveObject(x["id"], x.get("resourceKey"), x["name"]))
|
||||
else:
|
||||
# files.append(f"{x['id']}/{x['name']}")
|
||||
files.append(DriveObject(x["id"], x["name"]))
|
||||
files.append(DriveObject(x["id"], x.get("resourceKey"), x["name"]))
|
||||
|
||||
return folders, files
|
||||
|
||||
def get_meta(self, name):
|
||||
file_id = self.parse_id(name)
|
||||
url = f"{FILES_API}/{file_id}?key={self.api_key}"
|
||||
return self.get_json(url)
|
||||
file_resource = self.parse_resource(name)
|
||||
url = f"{FILES_API}/{file_resource.id}?key={self.api_key}"
|
||||
return self.get_json(url, file_resource)
|
||||
|
||||
def open(self, name, mode="rb"):
|
||||
file_id = self.parse_id(name)
|
||||
url = f"{FILES_API}/{file_id}?alt=media&key={self.api_key}"
|
||||
logger.info("URL: %s", url)
|
||||
response = requests.get(url, stream=True)
|
||||
resource = self.parse_resource(name)
|
||||
url = f"{FILES_API}/{resource.id}?alt=media&key={self.api_key}"
|
||||
|
||||
headers = {}
|
||||
if resource.key:
|
||||
headers["X-Goog-Drive-Resource-Keys"] = f"{resource.id}/{resource.key}"
|
||||
logger.info("URL: %s [%r]", url, headers)
|
||||
|
||||
response = requests.get(url, headers=headers, stream=True)
|
||||
return GzipFile(name, "rb", 9, response.raw)
|
||||
|
||||
def size(self, name):
|
||||
@@ -91,25 +109,28 @@ class GDriveLinkStorage(Storage):
|
||||
|
||||
def url(self, name):
|
||||
logger.debug("URL: %r", name)
|
||||
file_id = self.parse_id(name)
|
||||
return f"https://drive.usercontent.google.com/download?export=download&id={file_id}&confirm=yes"
|
||||
resource = self.parse_resource(name)
|
||||
uri = f"https://drive.usercontent.google.com/download?export=download&id={resource.id}&confirm=yes"
|
||||
if resource.key:
|
||||
uri += f"&resourcekey=${resource.key}"
|
||||
return uri
|
||||
|
||||
def get_folder_id(self, url):
|
||||
try:
|
||||
return self.extract_id(url, SHARED_FOLDER)
|
||||
return self.extract_resource(url, SHARED_FOLDER)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
def get_file_id(self, url):
|
||||
try:
|
||||
return self.extract_id(url, SHARED_FILE)
|
||||
return self.extract_resource(url, SHARED_FILE)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
def import_link(self, url) -> str:
|
||||
file_id = self.extract_id(url, SHARED_FILE)
|
||||
meta = self.get_meta(file_id)
|
||||
return f"{file_id}/{meta['name']}"
|
||||
file_resource = self.extract_resource(url, SHARED_FILE)
|
||||
meta = self.get_meta(file_resource)
|
||||
return f"{file_resource}/{meta['name']}"
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -6,6 +6,8 @@ from polyphonic.library.models import Work, Document
|
||||
from polyphonic.library import forms
|
||||
from byostorage.models import UserStorage
|
||||
|
||||
from . import sync_work
|
||||
|
||||
|
||||
class WorkGDriveView(CollectionMixin, SingleObjectMixin, FormView):
|
||||
model = Work
|
||||
@@ -37,11 +39,13 @@ class WorkGDriveView(CollectionMixin, SingleObjectMixin, FormView):
|
||||
self.object.meta_info.update_or_create(
|
||||
name="folderid", defaults={"value": folderid}
|
||||
)
|
||||
sync_work(self.object)
|
||||
|
||||
return redirect("work_detail", self.collection.pk, self.kwargs["pk"])
|
||||
|
||||
link = storage.import_link(link)
|
||||
if link is None:
|
||||
form.add_error("link", str(e))
|
||||
form.add_error("link", "Not a valid link")
|
||||
return self.form_invalid(form)
|
||||
|
||||
work = self.collection.works.get(pk=self.kwargs["pk"])
|
||||
|
||||
@@ -366,8 +366,8 @@ class Work(models.Model):
|
||||
|
||||
def music_tags(self):
|
||||
tags = dict(self.orchestration.as_list())
|
||||
for section in Section.objects.filter(doc__work_id=self.pk):
|
||||
tags.setdefault(section.tag, section.name)
|
||||
# for section in Section.objects.filter(doc__work_id=self.pk):
|
||||
# tags.setdefault(section.tag, section.name)
|
||||
|
||||
return tags.items()
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import re
|
||||
GENERAL = """
|
||||
mvmt Movement
|
||||
ex Excerpt
|
||||
sec Section
|
||||
sect Section
|
||||
pce Piece
|
||||
no Number
|
||||
no No.
|
||||
page Page
|
||||
"""
|
||||
|
||||
# taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_MusicTags
|
||||
@@ -19,6 +20,10 @@ cb Double Bass
|
||||
mall Mallet Percussion
|
||||
vln Violin
|
||||
vla Viola
|
||||
kit Drumkit
|
||||
asax Alto Sax
|
||||
tsax Tenor Sax
|
||||
bsax Bari Sax
|
||||
|
||||
acc Accordion
|
||||
afl Alto flute
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block page %}
|
||||
<h3 class="title">Library collections for {% firstof request.user.first_name request.user.username %}</h3>
|
||||
@@ -10,7 +11,9 @@
|
||||
<input class="input" name="q" type="text" placeholder="Find a work" value="{{ request.GET.filter }}"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button" href="?"><i class="fas fa-times"></i></a>
|
||||
<a class="button" href="?">
|
||||
{% icon "close" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -47,4 +50,5 @@
|
||||
<div>
|
||||
<small>{{ ensemble.ensemble_code }}</small>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,53 +1,110 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="#" onclick="saveTags()" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-save"></i></span>
|
||||
{% icon "save" %}
|
||||
<span>Save</span>
|
||||
</a>
|
||||
<a href="{% url 'work_detail' collection=collection.pk pk=object.work_id %}" class="button is-link is-light">
|
||||
{% icon "backspace" %}
|
||||
<span>Cancel</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block media %}
|
||||
<style>
|
||||
#unassigned-area {
|
||||
max-width: 200px;
|
||||
}
|
||||
#unassigned-area .tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
#area-left h3 {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.tag-grid {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.grid-column {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.grid-page {
|
||||
height: 25px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
border-top: 1px solid #DDD;
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.grid-page:last-child {
|
||||
border-bottom: 1px solid #DDD;
|
||||
height: 26px;
|
||||
}
|
||||
.grid-page.is-active {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
/*background-color: var(--bulma-link-light);*/
|
||||
color: var(--bulma-link);
|
||||
background-color: #EEE;
|
||||
border-radius: 0px 6px 6px 0px;
|
||||
}
|
||||
.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;
|
||||
background: #fff;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
border-left: none;
|
||||
padding-left: 0.5em;
|
||||
min-width: 100px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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 {
|
||||
min-width: 220px;
|
||||
position: relative;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.instrument {
|
||||
display: list-item !important;
|
||||
@@ -62,28 +119,34 @@
|
||||
{% block page %}
|
||||
<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 class="column is-narrow">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<button class="button is-small is-primary" id="prev"><</button>
|
||||
<div class="column is-narrow" id="area-left">
|
||||
<h3>Page control</h3>
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<button class="button is-small is-primary" id="prev"><</button>
|
||||
</div>
|
||||
<div class="column has-text-centered">
|
||||
<span id="page-num">-</span> / <span id="page-count">-</span>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<button class="button is-small is-primary" id="next">></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span id="page-num">-</span> / <span id="page-count">-</span>
|
||||
<h3>Quick Tags</h3>
|
||||
<ul id="unassigned-area">
|
||||
{% for tag, inst in document.work.music_tags %}
|
||||
<li class="tag is-warning" onclick="assignTag('{{tag}}', this)">{{ inst }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<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 class="column is-narrow">
|
||||
<button class="button is-small is-primary" id="next">></button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="unassigned-area">
|
||||
{% for tag, inst in document.work.music_tags %}
|
||||
<li class="is-clickable" onclick="assignInstrument('{{tag}}', this)")>{{ inst }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a onclick="document.getElementById('add-modal').classList.add('is-active')">Add Tag</a>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +159,6 @@
|
||||
<div class="grid-column" id="tag-area">
|
||||
</div>
|
||||
</div>
|
||||
<button class="button is-primary" style="width: 100%" onclick="expandEntries()">Expand</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,24 +167,46 @@
|
||||
<div class="modal-background" onclick="closeAddModal()"></div>
|
||||
<div class="modal-card">
|
||||
<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>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field has-addons">
|
||||
<div class="field has-addons is-justify-content-center">
|
||||
<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">
|
||||
{% 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 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 class="button is-primary" onclick="addInstrument();">Add</button>
|
||||
<button class="button is-primary" onclick="createNewTag();">Add</button>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -131,6 +215,15 @@
|
||||
|
||||
<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 %}
|
||||
|
||||
{% block scripts %}
|
||||
@@ -160,7 +253,11 @@
|
||||
pageNumPending = null,
|
||||
scale = 1,
|
||||
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.
|
||||
@@ -245,6 +342,7 @@
|
||||
pdfDoc = pdfDoc_;
|
||||
|
||||
const pageList = document.getElementById('page-list');
|
||||
const tagGrid = document.querySelector('.tag-grid');
|
||||
pageList.innerHTML = '';
|
||||
for (var i=0; i<pdfDoc.numPages; i++) {
|
||||
let page = i+1;
|
||||
@@ -258,10 +356,47 @@
|
||||
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;
|
||||
|
||||
// Initial/first page rendering
|
||||
renderPage(pageNum);
|
||||
setTimeout(() => {
|
||||
const previewBox = document.querySelector('.box');
|
||||
tagGrid.style.maxHeight = previewBox.offsetHeight + 'px';
|
||||
}, 100);
|
||||
|
||||
tagArea.innerHTML = '';
|
||||
for (let pageTag of data.pageTags) {
|
||||
@@ -270,105 +405,221 @@
|
||||
dirty = false;
|
||||
});
|
||||
|
||||
function closeAddModal() {
|
||||
document.getElementById('add-modal').classList.remove('is-active');
|
||||
document.getElementById('add-instrument-name').value = "";
|
||||
document.getElementById('add-instrument-variant').value = "";
|
||||
function showAddModal() {
|
||||
document.getElementById("add-modal").classList.add("is-active");
|
||||
changeTagType();
|
||||
}
|
||||
|
||||
function addInstrument() {
|
||||
let name = document.getElementById('add-instrument-name');
|
||||
let variant = document.getElementById('add-instrument-variant');
|
||||
function closeAddModal() {
|
||||
document.getElementById('add-modal').classList.remove('is-active');
|
||||
}
|
||||
|
||||
function addNumberedInstrument(tag, e) {
|
||||
let modal = document.getElementById('add-modal');
|
||||
|
||||
let inst_name = name.value;
|
||||
var tag = null;
|
||||
for (let key in data.instruments) {
|
||||
if (data.instruments[key] == inst_name) {
|
||||
tag = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.getElementById("add-instrument-name").value = data.instruments[tag];
|
||||
document.getElementById("add-instrument-variant").value = 1;
|
||||
document.getElementById("add-instrument-variant").focus();
|
||||
|
||||
if (!tag) {
|
||||
alert("Unknown tag: " + name);
|
||||
return;
|
||||
}
|
||||
modal.classList.add('is-active');
|
||||
}
|
||||
|
||||
if (variant.value) {
|
||||
tag += "-" + variant.value;
|
||||
}
|
||||
|
||||
variant.value = '';
|
||||
name.value = '';
|
||||
|
||||
let li = document.createElement('li');
|
||||
li.classList.add("is-clickable");
|
||||
li.addEventListener('click', () => assignInstrument(tag, li));
|
||||
li.innerHTML = get_instrument(tag);
|
||||
document.getElementById('unassigned-area').appendChild(li);
|
||||
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_select.focus();
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
for (let key in data.instruments) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createNewTag() {
|
||||
|
||||
const tag = generateNewTag()
|
||||
|
||||
addTag(tag, pageNum, pageNum);
|
||||
closeAddModal();
|
||||
}
|
||||
|
||||
function assignInstrument(tag, el) {
|
||||
function assignTag(tag, el) {
|
||||
addTag(tag, pageNum, pageNum);
|
||||
//el.remove();
|
||||
}
|
||||
|
||||
function addTag(tag, start, end) {
|
||||
console.log("addTag", tag, start, end);
|
||||
const el = document.createElement('div');
|
||||
el.className = 'grid-tag';
|
||||
el.dataset.tag = tag;
|
||||
|
||||
|
||||
//console.log("addTag", tag, start, end);
|
||||
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.end = end;
|
||||
el.dataset.tag = tag;
|
||||
|
||||
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);
|
||||
|
||||
const parts = tag.split("-")
|
||||
el.classList.add("tag-type-" + parts[0]);
|
||||
|
||||
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;
|
||||
const handle = el.querySelector('.tag-handle');
|
||||
handle.draggable = true;
|
||||
handle.addEventListener('dragstart', (e) => {
|
||||
draggedTag = el;
|
||||
dragMode = 'move';
|
||||
dragOrigStart = parseInt(el.dataset.start);
|
||||
dragOrigEnd = parseInt(el.dataset.end);
|
||||
el.style.zIndex = 1000;
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
const blankImg = new Image();
|
||||
blankImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
e.dataTransfer.setDragImage(blankImg, 0, 0);
|
||||
});
|
||||
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;
|
||||
});
|
||||
label.appendChild(del)
|
||||
|
||||
el.appendChild(label);
|
||||
|
||||
const resize = el.querySelector('.tag-handle-resize');
|
||||
resize.draggable = true;
|
||||
resize.addEventListener('dragstart', (e) => {
|
||||
draggedTag = el;
|
||||
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;
|
||||
});
|
||||
|
||||
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);
|
||||
resolveOverlaps();
|
||||
}
|
||||
|
||||
function updateTag(tag) {
|
||||
let start = tag.dataset.start;
|
||||
let end = tag.dataset.end;
|
||||
let span = end-start+1;
|
||||
let height = span * 25 + (span-1) * 5;
|
||||
let top = (start-1) * 30;
|
||||
let height = span * 25 + 1;
|
||||
let top = (start-1) * 25;
|
||||
|
||||
tag.style.height = height + 'px';
|
||||
tag.style.marginTop = top + 'px';
|
||||
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) {
|
||||
@@ -390,6 +641,10 @@
|
||||
}
|
||||
|
||||
function expandEntries() {
|
||||
const answer = confirm("This will expand all tags to fill up available pages");
|
||||
if (!answer) return;
|
||||
|
||||
|
||||
const entries = Array.from(tagArea.children);
|
||||
entries.sort((a, b) => a.dataset.start-b.dataset.start);
|
||||
const c = entries.length;
|
||||
@@ -402,6 +657,33 @@
|
||||
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() {
|
||||
const pageTags = [];
|
||||
for (let pageTag of tagArea.children ) {
|
||||
@@ -430,9 +712,9 @@
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
function get_instrument(s) {
|
||||
function get_tag_name(s) {
|
||||
let parts = s.split('-');
|
||||
let instrument = data.instruments[parts[0]];
|
||||
let instrument = data.instruments[parts[0]] || parts[0];
|
||||
if (parts.length == 2) {
|
||||
return instrument + " " + parts[1];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{% load path_filters %}
|
||||
{% load polyphonic %}
|
||||
<tr>
|
||||
<td><a href="{{ doc.upload.url }}" target="_blank">
|
||||
{{ doc.upload.name|basename }}</a></td>
|
||||
<td style="white-space: nowrap">
|
||||
<a href="{{ doc.upload.url }}" target="_blank">
|
||||
{{ doc.upload.name|basename }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% for section in doc.sections.all %}
|
||||
<a class="tag is-{{ section.bulma_class }}" target="_blank" href="{% url 'part_download' collection.pk section.pk section.filename %}">{{ section.name }}</a>
|
||||
@@ -10,10 +13,13 @@
|
||||
<td class="has-text-right" style="white-space: nowrap;">
|
||||
{% if request.is_admin %}
|
||||
{% if doc.doctype == 1 %}
|
||||
<a href="{% url 'document_annotate' collection.pk doc.pk %}"><i class="fas fa-tags"
|
||||
title="Manage Tags"></i></a>
|
||||
<a href="{% url 'document_annotate' collection.pk doc.pk %}" title="Annotate">
|
||||
{% icon "toc" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'document_delete' collection.pk doc.pk %}"><i class="fas fa-trash-alt" title="Delete Document"></i></a>
|
||||
<a href="{% url 'document_delete' collection.pk doc.pk %}" title="Delete">
|
||||
{% icon "delete" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'work_detail' collection=collection.pk pk=object.pk %}" class="button is-link is-light">
|
||||
{% icon "backspace" %}
|
||||
<span>Back to work</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<h2 class="title">
|
||||
{% icon "add_to_drive" %}
|
||||
<span>Google Drive - Shared Links
|
||||
</h2>
|
||||
<h3 class="subtitle"><a href="{% url 'work_detail' collection.pk object.pk %}">{{ object.name }}</a></h3>
|
||||
<div class="m-3">
|
||||
<p>This page lets you link a work to a google drive folder. You can either paste a public link to a folder to enable syncing or a file to add individually</p>
|
||||
@@ -14,26 +20,24 @@
|
||||
<div class="m-3">
|
||||
<p>
|
||||
{% if meta.folderid %}
|
||||
This work is currently linked to <b>{{ meta.folderid }}</b>. Pasting a new folder link will overwrite this.
|
||||
This work is currently linked to <b>{{ meta.folderid }}</b>.<br/>
|
||||
Pasting a new folder link will overwrite this.
|
||||
{% else %}
|
||||
There is currently no shared drive folder linked to this work - paste one here to enable syncing.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-5">
|
||||
<form method="post">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input name="link" class="input is-expanded" type="text" placeholder="Shared link">
|
||||
</div>
|
||||
{% for error in form.errors.link %}
|
||||
<p class="help is-danger">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-info" type="submit">Add</button>
|
||||
</div>
|
||||
<div class="field has-addons mx-6">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" name="link" type="text" placeholder="Paste shared link">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" type="submit">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block page %}
|
||||
<form action="" method="post" target="_blank">
|
||||
@@ -31,12 +32,6 @@
|
||||
</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>
|
||||
|
||||
@@ -45,6 +40,7 @@
|
||||
<tr>
|
||||
<th/>
|
||||
<th>Piece</th>
|
||||
<th>Composer</th>
|
||||
<th class="is-hidden-mobile">Running time</th>
|
||||
<th>Part</th>
|
||||
<th/>
|
||||
@@ -61,11 +57,12 @@
|
||||
{{ item.work.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="is-hidden-mobile">{{ item.work.composer }}</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%">
|
||||
<select name="instruments" id="part-{{ item.work.pk }}" style="width: 100%">
|
||||
<select name="instrument-selection" id="part-{{ item.work.pk }}" style="width: 100%">
|
||||
<option value='-'>None</option>
|
||||
{% for tag, name in item.work.digital_parts %}
|
||||
<option value='{{ tag }}'>{{ name }}</option>
|
||||
@@ -74,8 +71,9 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="is-action" onclick="downloadPart({{ item.work.collection_id }}, {{ item.work.pk }})">
|
||||
<i class="fas fa-download" title="Download Part"></i>
|
||||
<span class="button is-link is-small" onclick="downloadPart({{ item.work.collection_id }}, {{ item.work.pk }})">
|
||||
{% icon "download" %}
|
||||
<span>Get</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -85,13 +83,17 @@
|
||||
<tr>
|
||||
<td/>
|
||||
<td/>
|
||||
<td>{% firstof 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>{% firstof running_time "------" %}</td>
|
||||
<td colspan="2">
|
||||
<button class="button is-link is-small" type="submit" name="action" value="pdf">
|
||||
{% icon "two_pager" %}
|
||||
<span> Single combined PDF</span>
|
||||
</button>
|
||||
<button class="button is-link is-small" type="submit" name="action" value="zip">
|
||||
{% icon "folder_zip" %}
|
||||
<span> Individual files (zipped)</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -112,16 +114,23 @@
|
||||
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 of INSTRUMENTS) {
|
||||
if (i[1] === inst) inst = i[0];
|
||||
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:", part);
|
||||
|
||||
|
||||
selectParts(inst, part);
|
||||
@@ -132,10 +141,11 @@ function selectParts(inst, part) {
|
||||
|
||||
let prefix = inst + "-" + part;
|
||||
|
||||
let instruments = document.getElementsByName("instruments");
|
||||
for(let i=0; i<instruments.length; i++) {
|
||||
let selections = document.getElementsByName("instrument-selection");
|
||||
console.log("Updating selections:", prefix, selections);
|
||||
for(let i=0; i<selections.length; i++) {
|
||||
var result = "-"
|
||||
let options = instruments[i].children;
|
||||
let options = selections[i].children;
|
||||
for(let j=0; j<options.length; j++) {
|
||||
let value = options[j].value;
|
||||
if (value.startsWith(prefix)) {
|
||||
@@ -146,7 +156,8 @@ function selectParts(inst, part) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
instruments[i].value = result;
|
||||
console.log("Selected:", result);
|
||||
selections[i].value = result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -168,4 +179,4 @@ updateParts();
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% 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>
|
||||
{% icon "add_circle" %}
|
||||
<span>Add</span>
|
||||
</a>
|
||||
<a href="#" onclick="save()" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-save"></i></span>
|
||||
{% icon "save" %}
|
||||
<span>Save</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -26,9 +27,15 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -106,4 +113,4 @@ function checkSaved(e) {
|
||||
window.addEventListener('beforeunload', checkSaved);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends 'interface/project_base.html' %}
|
||||
{% load path_filters %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block media %}
|
||||
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
|
||||
@@ -8,12 +9,12 @@
|
||||
|
||||
{% block admin %}
|
||||
<a href="{% url 'work_edit' collection.pk work.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-edit"></i></span>
|
||||
<span>Edit</span>
|
||||
{% icon "edit" %}
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
<a href="{% url 'work_add_to_project' collection.pk work.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
<span>Add to project</span>
|
||||
{% icon "add_ad" %}
|
||||
<span>Add to project</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -68,8 +69,8 @@
|
||||
<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
|
||||
{% icon "menu_book" %}
|
||||
<span>Printed Parts</span>
|
||||
</h4>
|
||||
<div class="tags">
|
||||
{% for inst, c in work.physical_parts %}
|
||||
@@ -81,8 +82,8 @@
|
||||
</div>
|
||||
<div class="box">
|
||||
<h4 class="subtitle is-size-4">
|
||||
<span class="icon"><i class="fas fa-print"></i></span>
|
||||
Digital Parts
|
||||
{% icon "file_copy" %}
|
||||
<span>Digital Parts</span>
|
||||
</h4>
|
||||
<div class="tags">
|
||||
{% with sections=work.digital_parts %}
|
||||
@@ -105,8 +106,8 @@
|
||||
<div class="box">
|
||||
<div class="level">
|
||||
<h4 class="subtitle is-size-4">
|
||||
<span class="icon"><i class="fas fa-file"></i></span>
|
||||
Files
|
||||
{% icon "file_present" %}
|
||||
<span>Files<span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@@ -136,9 +137,9 @@
|
||||
{% endif %}
|
||||
{% if "gdrive" in methods %}
|
||||
<div class="has-text-centered mt-3">
|
||||
<a class="button button-primary is-size-7" href="{% url 'work_gdrive' collection.pk object.pk %}">
|
||||
<span class="icon"><i class="fa-brands fa-google-drive"></i></span>
|
||||
Link Google Drive Files
|
||||
<a class="button button-is-primary is-size-7" href="{% url 'work_gdrive' collection.pk object.pk %}">
|
||||
{% icon "add_to_drive" %}
|
||||
<span>Google Drive</span>
|
||||
</a><br/>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -152,12 +153,14 @@
|
||||
<div class="box">
|
||||
<div class="level">
|
||||
<h4 class="is-size-4">
|
||||
<span class="icon"><i class="fas fa-book-reader"></i></span>
|
||||
Loans
|
||||
{{ "folder_open"| icon }}
|
||||
Projects
|
||||
</h4>
|
||||
<span class="level-right">
|
||||
<a class="icon-text" href="{% url 'work_add_to_project' collection.pk work.pk %}"><span class="icon"><i
|
||||
class="fas fa-plus-circle"></i></span> Checkout</a>
|
||||
<a class="icon-text" href="{% url 'work_add_to_project' collection.pk work.pk %}">
|
||||
{% icon "shopping_cart_checkout" %}
|
||||
<span>Checkout</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<table class="table is-fullwidth">
|
||||
@@ -181,7 +184,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td>No current loans</td>
|
||||
<td>No current assignments</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load url_tools %}
|
||||
{% load polyphonic %}
|
||||
|
||||
{% block admin %}
|
||||
{% if collection %}
|
||||
<a href="{% url 'work_add' collection.pk %}" class="button is-link">
|
||||
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||
{% icon "add_notes" %}
|
||||
<span>Add a work</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -18,7 +19,9 @@
|
||||
<input class="input" name="q" type="text" placeholder="Filter" value="{{ request.GET.q }}"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button" href="?"><i class="fas fa-times"></i></a>
|
||||
<a class="button" href="?">
|
||||
{% icon "close" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -65,6 +68,7 @@
|
||||
{% else %}
|
||||
disabled
|
||||
{% endif %}>
|
||||
{% icon "arrow_back" %}
|
||||
Previous</a>
|
||||
<a class="pagination-next"
|
||||
{% if page_obj.has_next %}
|
||||
@@ -73,6 +77,7 @@
|
||||
disabled
|
||||
{% endif %}>
|
||||
Next
|
||||
{% icon "arrow_forward" %}
|
||||
</a>
|
||||
<ul class="pagination-list">
|
||||
{% for page in page_range %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "interface/project_base.html" %}
|
||||
{% load polyphonic %}
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +36,14 @@
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">
|
||||
<span class="icon"><i class="fas fa-print"></i></span>
|
||||
{% icon "print" %}
|
||||
<span>Print Set</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-link is-light" href="{% url 'work_detail' collection.pk object.pk %}">
|
||||
<span>Cancel</span>
|
||||
{% icon "backspace" %}
|
||||
<span>Cancel</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,4 +52,4 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -23,7 +23,7 @@ from polyphonic.library.models import Collection, Work, Document, Section
|
||||
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 indexer, model_search
|
||||
from polyphonic.library.indexer import index_works, model_search
|
||||
|
||||
import logging
|
||||
|
||||
@@ -35,10 +35,10 @@ class ProjectItemListView(ProjectMixin, ListView):
|
||||
model = models.ProjectItem
|
||||
|
||||
def post(self, request: HttpRequest, **kwargs):
|
||||
|
||||
project_works = self.project.works.all()
|
||||
|
||||
instruments = request.POST.getlist("instruments")
|
||||
print(request.POST)
|
||||
instruments = request.POST.getlist("instrument-selection")
|
||||
works = request.POST.getlist("works")
|
||||
|
||||
request.session["part"] = request.POST.get("part", "")
|
||||
@@ -65,13 +65,16 @@ class ProjectItemListView(ProjectMixin, ListView):
|
||||
(part.doc.upload.path, part.doc.work.name, part.start, part.end, 1)
|
||||
)
|
||||
|
||||
result = extract_and_concat(sections)
|
||||
action = request.POST.get("action")
|
||||
if action == "pdf":
|
||||
result = extract_and_concat(sections)
|
||||
download_name = f"{self.project.name}.pdf"
|
||||
|
||||
download_name = f"{self.project.name}.pdf"
|
||||
response = FileResponse(result, content_type="application/pdf")
|
||||
response["Content-Disposition"] = f'inline; filename="{download_name}"'
|
||||
return response
|
||||
|
||||
response = FileResponse(result, content_type="application/pdf")
|
||||
response["Content-Disposition"] = f'inline; filename="{download_name}"'
|
||||
return response
|
||||
return HttpResponse(f"Unknown action: {action}", status=400)
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
@@ -304,8 +307,7 @@ class WorkAddView(CollectionMixin, FormView):
|
||||
for f in uploads:
|
||||
docs.append(work.docs.create(upload=f).pk)
|
||||
|
||||
ix = indexer.get_index()
|
||||
indexer.index_works(ix, [work])
|
||||
index_works([work])
|
||||
|
||||
if len(docs) == 1:
|
||||
return redirect("document_annotate", docs[0])
|
||||
@@ -339,8 +341,7 @@ class WorkUpdateView(CollectionMixin, UpdateView):
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
|
||||
ix = indexer.get_index()
|
||||
indexer.index_works(ix, [self.object])
|
||||
index_works([self.object])
|
||||
|
||||
return response
|
||||
|
||||
@@ -595,6 +596,12 @@ class DocumentAnnotateView(DocumentMixin, DetailView):
|
||||
collection=data["collection"].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)}
|
||||
return data
|
||||
|
||||
@@ -26,12 +26,22 @@ dependencies = [
|
||||
django-debug-toolbar = "5.2"
|
||||
ruff = "^0.15.12"
|
||||
coverage = "^7.14.0"
|
||||
django-types = "^0.24.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
poly-tool = "polyphonic.manage:main"
|
||||
|
||||
[tool.ruff]
|
||||
extend-exclude = ["**/migrations/"]
|
||||
lint.extend-select = [
|
||||
#"DJ", # flake8-django: Django-specific bugs
|
||||
#"E", # pycodestyle errors
|
||||
"F", # Pyflakes
|
||||
#"W", # pycodestyle warnings
|
||||
#"I", # isort
|
||||
#"UP", # pyupgrade
|
||||
#"B", # flake8-bugbear
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
|
||||
Reference in New Issue
Block a user