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: