roundabout,
created on Saturday, 14 September 2024, 09:26:57 (1726306017),
received on Saturday, 14 September 2024, 09:36:45 (1726306605)
Author identity: vlad <vlad.muntoiu@gmail.com>
5580cab82885dd9aa4e89d1471b69b7846aca184
app.py
@@ -280,6 +280,16 @@ with app.app_context():
all_ratings = db.session.query(PictureRating.rating).filter_by(resource=self)
return {rating: all_ratings.filter_by(rating=rating).count() for rating in range(1, 6)}
@property
def stars(self):
if not self.ratings:
return 0
average = self.average_rating
whole_stars = int(average)
partial_star = average - whole_stars
return [100] * whole_stars + [int(partial_star * 100)] + [0] * (4 - whole_stars)
class PictureInGallery(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
static/style.css
@@ -10,6 +10,11 @@
--color-star: #FFC107;
--color-shape-label: #0097A7; --color-shape-label-text: #ffffff;
--shadow-card-inset: inset 0 3px 6px -4px rgba(0, 0, 0, 0.12), inset 0 3px 6px 0 rgba(0, 0, 0, 0.24), inset 0 1px 4px 0 rgba(0, 0, 0, 0.12);
--1-star: #FF8079;
--2-star: #FF8A65;
--3-star: #FFAC1B;
--4-star: #FBC02D;
--5-star: #CDDC39;
/*view-transition-name: root;*/
}
@@ -36,6 +41,7 @@ iconify-icon {
#annotation-zone, .annotation-zone {
position: relative;
user-select: none;
overflow: hidden;
}
#annotation-image, .annotation-image {
@@ -421,7 +427,6 @@ dd {
.star-rating-container {
display: flex;
flex-direction: row-reverse;
gap: 0.5ch;
font-size: 1.25em;
align-items: center;
justify-content: flex-end; /* In this case, it means LEFT, not right, because of the row-reverse */
@@ -449,3 +454,62 @@ dd {
.star-rating-container > input:checked ~ label {
color: var(--color-star);
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
box-sizing: content-box;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
border: 0;
}
.rating-list {
display: grid;
grid-auto-columns: 1fr;
gap: 0.5rem;
grid-template-columns: auto;
list-style: none;
margin: 0 !important;
padding: 0;
min-width: 50%;
}
.rating-list > li {
grid-column-start: 1;
height: 28px;
padding: 4px 8px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rch;
border-radius: 0 4px 4px 0;
margin: 0;
}
.rating-bar {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rch;
font-size: 1.25em;
width: 100%;
}
.rating-bar > .rating-bar-segment {
position: relative;
width: 1em;
height: 1em;
clip-path: url(#star-clip);
background: var(--text-softest);
}
.rating-bar > .rating-bar-segment > .rating-bar-filling {
background: var(--color-star);
height: 100%;
position: absolute;
top: 0;
left: 0;
}
templates/picture.html
@@ -8,6 +8,13 @@
{% endif %}
{% endmacro %}
{% block content %}
<svg width="0" height="0">
<defs>
<clipPath id="star-clip">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21z"></path>
</clipPath>
</defs>
</svg>
<x-frame style="--width: 768px">
<h1>{{ resource.title }}</h1>
<p>by <a href="/profile/{{ resource.author.username }}">{{ resource.author.formatted_name }}</a></p>
@@ -39,62 +46,85 @@
<a href="/picture/{{ resource.id }}/delete">Delete</a>
</details>
{% endif %}
<div id="annotation-zone">
<img id="annotation-image" src="/raw/picture/{{ resource.id }}" alt="{{ resource.title }}">
{% for region in resource.regions %}
{% if region.json.type == "bbox" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<rect x="{{ region.json.shape.x * size[0] }}"
y="{{ region.json.shape.y * size[1] }}"
width="{{ region.json.shape.w * size[0] }}"
height="{{ region.json.shape.h * size[1] }}"
fill="none" class="shape-bbox shape"
></rect>
{% set centre_x = region.json.shape.x + region.json.shape.w / 2 %}
{% set centre_y = region.json.shape.y + region.json.shape.h / 2 %}
</svg>
{% elif region.json.type == "polygon" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<polygon points="{% for point in region.json.shape %}{{ point.x * size[0] }},{{ point.y * size[1] }} {% endfor %}" fill="none" class="shape-polygon shape"></polygon>
{% set top = region.json.shape | sort(attribute='y') | last %}
{% set left = region.json.shape | sort(attribute='x') | first %}
{% set bottom = region.json.shape | sort(attribute='y') | first %}
{% set right = region.json.shape | sort(attribute='x') | last %}
{% set centre_x = (left.x + right.x) / 2 %}
{% set centre_y = (top.y + bottom.y) / 2 %}
</svg>
{% elif region.json.type == "polyline" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<polyline points="{% for point in region.json.shape %}{{ point.x * size[0] }},{{ point.y * size[1] }} {% endfor %}" fill="none" class="shape-polyline shape"></polyline>
{# Median point #}
{% set centre_x = region.json.shape | map(attribute="x") | median %}
{% set centre_y = region.json.shape | map(attribute="y") | median %}
</svg>
{% elif region.json.type == "point" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<circle cx="{{ region.json.shape.x * size[0] }}" cy="{{ region.json.shape.y * size[1] }}" r="0" fill="none" class="shape-point shape"></circle>
</svg>
{% endif %}
{{ shape_label(centre_x, centre_y, region.object_id) }}
{% endfor %}
</div>
<x-buttonbox>
<label>
<input type="checkbox" id="show-shapes" checked>
Show shapes
</label>
<label>
<input type="checkbox" id="show-objects" checked>
Show objects
</label>
</x-buttonbox>
{% set licences = resource.licences | map(attribute="licence") | list %}
{% set contains = resource.regions | map(attribute="object_id") | set | select | sort | list %}
<x-vbox>
<p>
{{ resource.rating_totals }} ratings, average {{ resource.average_rating }}
</p>
<div id="annotation-zone">
<img id="annotation-image" src="/raw/picture/{{ resource.id }}" alt="{{ resource.title }}">
{% for region in resource.regions %}
{% if region.json.type == "bbox" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<rect x="{{ region.json.shape.x * size[0] }}"
y="{{ region.json.shape.y * size[1] }}"
width="{{ region.json.shape.w * size[0] }}"
height="{{ region.json.shape.h * size[1] }}"
fill="none" class="shape-bbox shape"
></rect>
{% set centre_x = region.json.shape.x + region.json.shape.w / 2 %}
{% set centre_y = region.json.shape.y + region.json.shape.h / 2 %}
</svg>
{% elif region.json.type == "polygon" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<polygon points="{% for point in region.json.shape %}{{ point.x * size[0] }},{{ point.y * size[1] }} {% endfor %}" fill="none" class="shape-polygon shape"></polygon>
{% set top = region.json.shape | sort(attribute='y') | last %}
{% set left = region.json.shape | sort(attribute='x') | first %}
{% set bottom = region.json.shape | sort(attribute='y') | first %}
{% set right = region.json.shape | sort(attribute='x') | last %}
{% set centre_x = (left.x + right.x) / 2 %}
{% set centre_y = (top.y + bottom.y) / 2 %}
</svg>
{% elif region.json.type == "polyline" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<polyline points="{% for point in region.json.shape %}{{ point.x * size[0] }},{{ point.y * size[1] }} {% endfor %}" fill="none" class="shape-polyline shape"></polyline>
{# Median point #}
{% set centre_x = region.json.shape | map(attribute="x") | median %}
{% set centre_y = region.json.shape | map(attribute="y") | median %}
</svg>
{% elif region.json.type == "point" %}
<svg class="shape-container-viewonly" viewBox="0 0 {{ size[0] }} {{ size[1] }}">
<circle cx="{{ region.json.shape.x * size[0] }}" cy="{{ region.json.shape.y * size[1] }}" r="0" fill="none" class="shape-point shape"></circle>
</svg>
{% endif %}
{{ shape_label(centre_x, centre_y, region.object_id) }}
{% endfor %}
</div>
<x-buttonbox>
<label>
<input type="checkbox" id="show-shapes" checked>
Show shapes
</label>
<label>
<input type="checkbox" id="show-objects" checked>
Show objects
</label>
</x-buttonbox>
{% set licences = resource.licences | map(attribute="licence") | list %}
{% set contains = resource.regions | map(attribute="object_id") | set | select | sort | list %}
<h2>Ratings ({{ resource.rating_totals.values() | sum }})</h2>
<x-hbox>
<x-vbox>
<div class="rating-bar">
{% for i in range(1, 6) %}
<div class="rating-bar-segment">
<div class="rating-bar-filling" style="width: {{ resource.stars[i-1] }}%"></div>
</div>
{% endfor %}
</div>
<p>
<span>Average rating:</span>
<span>{{ resource.average_rating | round(2) }}</span>
from {{ resource.rating_totals.values() | sum }} ratings
</p>
</x-vbox>
<ul class="rating-list flexible-space">
{% for i in range(5, 0, -1) %}
<li style="grid-column-end: {{ resource.rating_totals[i] + 2 }}; background: var(--{{ i }}-star);">
<span>{{ i }}:</span>
<span>{{ resource.rating_totals[i] }}</span>
</li>
{% endfor %}
</ul>
</x-hbox>
{% if current_user %}
<h3>Your rating</h3>
<form id="rating-form" method="POST" action="/picture/{{ resource.id }}/rate">
<label>
<input name="rating" type="radio" value="0" {% if not own_rating.rating %}checked{% endif %}>
@@ -115,6 +145,7 @@
<button type="submit">Rate</button>
</form>
{% endif %}
<h2>Details</h2>
<div class="icon-explainer">
<span>Type</span>
<span>{{ resource.nature.id }}</span>
@@ -130,6 +161,7 @@
<span>{{ resource.timestamp }}</span>
</div>
Contains objects: {{ contains | join(", ") }}
<h2>Licensing</h2>
<x-hbox style="justify-content: space-between">
<small class="picture-licensing-info">
Available under: