Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
|
139225a1f6 | ||
|
19e4c62e50 | ||
|
c7f7ad4f1b | ||
|
b5a133a3c2 |
13 changed files with 264 additions and 0 deletions
|
@ -44,6 +44,7 @@ STATIC_ROOT = "static/"
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'counter.apps.CounterConfig',
|
'counter.apps.CounterConfig',
|
||||||
'user.apps.UserConfig',
|
'user.apps.UserConfig',
|
||||||
|
'score.apps.ScoreConfig',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
|
|
@ -22,6 +22,7 @@ from django.views.i18n import JavaScriptCatalog
|
||||||
|
|
||||||
urlpatterns = i18n_patterns(
|
urlpatterns = i18n_patterns(
|
||||||
path('', include('counter.urls')),
|
path('', include('counter.urls')),
|
||||||
|
path('', include('score.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
path('accounts/', include('user.urls')),
|
path('accounts/', include('user.urls')),
|
||||||
|
|
0
score/__init__.py
Normal file
0
score/__init__.py
Normal file
5
score/admin.py
Normal file
5
score/admin.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import Score, ScoreRow
|
||||||
|
|
||||||
|
admin.site.register(Score)
|
||||||
|
admin.site.register(ScoreRow)
|
5
score/apps.py
Normal file
5
score/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreConfig(AppConfig):
|
||||||
|
name = 'score'
|
0
score/migrations/__init__.py
Normal file
0
score/migrations/__init__.py
Normal file
41
score/models.py
Normal file
41
score/models.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.validators import RegexValidator, MaxValueValidator, \
|
||||||
|
MaxLengthValidator
|
||||||
|
|
||||||
|
|
||||||
|
class Score(models.Model):
|
||||||
|
TARGETS = [
|
||||||
|
('TR', _('40/60/80/120cm Target')),
|
||||||
|
('HF', _('H&F target'))
|
||||||
|
]
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
ends = models.PositiveSmallIntegerField(_('Number of ends in this score'),
|
||||||
|
validators=[MaxValueValidator(24)])
|
||||||
|
arrows_per_end = models.PositiveSmallIntegerField(
|
||||||
|
_('Number of arrows per end'), validators=[MaxValueValidator(12)])
|
||||||
|
date = models.DateField(_('Date of the score'))
|
||||||
|
notes = models.TextField(_('Additional notes'),
|
||||||
|
validators=[MaxLengthValidator(500)])
|
||||||
|
target = models.CharField(_('Type of target'), max_length=2,
|
||||||
|
choices=TARGETS)
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreRow(models.Model):
|
||||||
|
class Meta:
|
||||||
|
unique_together = [['score', 'end_number']]
|
||||||
|
|
||||||
|
score = models.ForeignKey(Score, on_delete=models.CASCADE)
|
||||||
|
arrow_list = models.CharField(_('Comma separated list of arrow scores'),
|
||||||
|
max_length=36,
|
||||||
|
validators=[RegexValidator(regex="^(\d|10)(,(\d|10)){2,11}$",
|
||||||
|
message=_("Value given is not a valid score row"))])
|
||||||
|
end_number = models.PositiveSmallIntegerField(_('End number'),
|
||||||
|
validators=[MaxValueValidator(24)])
|
||||||
|
|
49
score/static/img/WA_target.svg
Normal file
49
score/static/img/WA_target.svg
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.0" width="80cm" height="80cm" viewBox="-40 -40 80 80" id="svg2">
|
||||||
|
<metadata id="metadata18">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
<dc:title>Official FITA 80cm Archery Target</dc:title>
|
||||||
|
<dc:date>2006-06-01</dc:date>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Alberto Barbati</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/"/>
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>archery target</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</dc:subject>
|
||||||
|
<dc:description>An official FITA 80cm archery target.
|
||||||
|
Real colors are:
|
||||||
|
Yellow: Pantone 107U
|
||||||
|
Red: Pantone 032U
|
||||||
|
Light blue: Pantone 306U</dc:description>
|
||||||
|
</cc:Work>
|
||||||
|
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
|
||||||
|
<cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
|
||||||
|
<cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
|
||||||
|
<cc:requires rdf:resource="http://web.resource.org/cc/Notice"/>
|
||||||
|
<cc:requires rdf:resource="http://web.resource.org/cc/Attribution"/>
|
||||||
|
<cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
|
||||||
|
<cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/>
|
||||||
|
</cc:License>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<circle cx="0" cy="0" r="39.9" fill="white" stroke="black" stroke-width="0.2" id="Zone1"/>
|
||||||
|
<circle cx="0" cy="0" r="35.9" fill="white" stroke="black" stroke-width="0.2" id="Zone2"/>
|
||||||
|
<circle cx="0" cy="0" r="32" fill="black" id="Zone3"/>
|
||||||
|
<circle cx="0" cy="0" r="27.9" fill="black" stroke="white" stroke-width="0.2" id="Zone4"/>
|
||||||
|
<circle cx="0" cy="0" r="24" fill="#41b7c8" id="Zone5"/>
|
||||||
|
<circle cx="0" cy="0" r="19.9" fill="#41b7c8" stroke="black" stroke-width="0.2" id="Zone6"/>
|
||||||
|
<circle cx="0" cy="0" r="15.9" fill="#fd1b14" stroke="black" stroke-width="0.2" id="Zone7"/>
|
||||||
|
<circle cx="0" cy="0" r="11.9" fill="#fd1b14" stroke="black" stroke-width="0.2" id="Zone8"/>
|
||||||
|
<circle cx="0" cy="0" r="7.9" fill="#fff535" stroke="black" stroke-width="0.2" id="Zone9"/>
|
||||||
|
<circle cx="0" cy="0" r="3.9" fill="#fff535" stroke="black" stroke-width="0.2" id="Zone10"/>
|
||||||
|
<circle cx="0" cy="0" r="1.9" fill="#fff535" stroke="black" stroke-width="0.1" id="Inner10"/>
|
||||||
|
<path d="M -0.2 0 L 0.2 0 M 0 -0.2 L 0 0.2" stroke="black" stroke-width="0.1" id="Center"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
76
score/static/js/score.js
Normal file
76
score/static/js/score.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// vim: set ts=2 sw=2 et tw=80:
|
||||||
|
|
||||||
|
$(document).ready(() => {
|
||||||
|
const TARGET_EVENTS = $('.target-events');
|
||||||
|
const SCORES = [
|
||||||
|
{ name: 'X', maxRadius: 0.025 },
|
||||||
|
{ name: '10', maxRadius: 0.05 },
|
||||||
|
{ name: '9', maxRadius: 0.1 },
|
||||||
|
{ name: '8', maxRadius: 0.15 },
|
||||||
|
{ name: '7', maxRadius: 0.2 },
|
||||||
|
{ name: '6', maxRadius: 0.25 },
|
||||||
|
{ name: '5', maxRadius: 0.3 },
|
||||||
|
{ name: '4', maxRadius: 0.35 },
|
||||||
|
{ name: '3', maxRadius: 0.4 },
|
||||||
|
{ name: '2', maxRadius: 0.45 },
|
||||||
|
{ name: '1', maxRadius: 0.5 }
|
||||||
|
];
|
||||||
|
|
||||||
|
function score(radius) {
|
||||||
|
for (let score of SCORES) {
|
||||||
|
if (radius <= score.maxRadius) {
|
||||||
|
return score.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'M';
|
||||||
|
};
|
||||||
|
|
||||||
|
TARGET_EVENTS.on('mousemove', e => {
|
||||||
|
if (TARGET_EVENTS.data('dragging')) {
|
||||||
|
const left = e.originalEvent.pageX - TARGET_EVENTS.offset().left;
|
||||||
|
const top = e.originalEvent.pageY - TARGET_EVENTS.offset().top;
|
||||||
|
markScore(left, top, TARGET_EVENTS.data('dragging')[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TARGET_EVENTS.on('mouseup', e => {
|
||||||
|
let $marker = TARGET_EVENTS.data('dragging');
|
||||||
|
if ($marker) {
|
||||||
|
$marker.removeClass('orange');
|
||||||
|
$marker.addClass('teal');
|
||||||
|
TARGET_EVENTS.data('dragging', null);
|
||||||
|
} else {
|
||||||
|
markScore(e.originalEvent.offsetX, e.originalEvent.offsetY, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function markScore(left, top, marker) {
|
||||||
|
const targetRadius = TARGET_EVENTS.width();
|
||||||
|
|
||||||
|
const x = -0.5 + left / targetRadius;
|
||||||
|
const y = -0.5 + top / targetRadius;
|
||||||
|
|
||||||
|
const radius = Math.sqrt(x * x + y * y);
|
||||||
|
|
||||||
|
if (!marker) {
|
||||||
|
marker = document.createElement("div");
|
||||||
|
|
||||||
|
const $marker = $(marker);
|
||||||
|
$marker.on('mousedown', e => {
|
||||||
|
TARGET_EVENTS.data('dragging', $marker);
|
||||||
|
$marker.removeClass('teal');
|
||||||
|
$marker.addClass('orange');
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
marker.className = "score-marker teal";
|
||||||
|
|
||||||
|
TARGET_EVENTS.append(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
marker.style.left = (x + 0.5) * 100 + "%";
|
||||||
|
marker.style.top = (y + 0.5) * 100 + "%";
|
||||||
|
|
||||||
|
console.log(x, y, score(radius));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
70
score/templates/score/edit.html
Normal file
70
score/templates/score/edit.html
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
{# vim: set ts=2 sw=2 et tw=80: #}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Update score" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{% static "js/score.js" %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
<style>
|
||||||
|
.target-container {
|
||||||
|
max-width: 450px;
|
||||||
|
max-height: 450px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-events, #target-svg {
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-events {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-marker {
|
||||||
|
position: absolute;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin-top: -.5em;
|
||||||
|
margin-left: -.5em;
|
||||||
|
border-radius: .5em;
|
||||||
|
border: 1px solid black;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="center edit-arrowcount title">{% trans "Update score" %}</h1>
|
||||||
|
<div class="card-panel grey lighten-1 target">
|
||||||
|
<div class='target-container'>
|
||||||
|
<div class='target-events'></div>
|
||||||
|
<object id="target-svg"
|
||||||
|
type="image/svg+xml" data="{% static "img/WA_target.svg" %}">
|
||||||
|
</object>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card grey lighten-2">
|
||||||
|
<div class="card-content center">
|
||||||
|
<span class="card-title">{% trans "Score" %}</span>
|
||||||
|
<p>lorem</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
3
score/tests.py
Normal file
3
score/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
7
score/urls.py
Normal file
7
score/urls.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from django.urls import path
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('score/edit/<int:sc_id>', views.score_edit, name='score_edit'),
|
||||||
|
]
|
6
score/views.py
Normal file
6
score/views.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def score_edit(request, sc_id):
|
||||||
|
return render(request, 'score/edit.html', {})
|
Loading…
Reference in a new issue