roundabout,
created on Saturday, 14 September 2024, 10:20:40 (1726309240),
received on Saturday, 14 September 2024, 10:31:15 (1726309875)
Author identity: vlad <vlad.muntoiu@gmail.com>
0725b464587632a649c1e21624171f6c7cfdf23e
static/style.css
@@ -513,3 +513,69 @@ dd {
top: 0; left: 0; } #picture-view { display: flex; flex-direction: row; gap: 1em; align-items: flex-start; justify-content: center; width: fit-content; margin: 0 auto; } #picture-actions { --width: 256px; position: sticky; top: 0; overflow: auto; max-height: 100vh; height: 100%; background: var(--color-card); padding: 1rem 0; box-shadow: var(--shadow-card); } @media screen and (max-width: 768px) { #picture-view { flex-direction: column; } #picture-actions { --width: 768px; position: static; padding: 1rem; } #picture-actions > ul { width: 100%; list-style: none; margin: 0; padding: 0; } #picture-actions > ul > li { display: inline; } #picture-actions > ul > li:not(:last-child)::after { content: " | "; } .action-list > li { padding: 0 !important; } } .action-list { list-style: none; margin: 0 !important; padding: 0; } .action-list > li { display: flex; margin: 0; width: 100%; padding: 0.25rem 1rem; } .action-list > li > a { text-decoration: none; color: var(--color-card-text) !important; }
templates/picture.html
@@ -8,225 +8,255 @@
{% endif %} {% endmacro %} {% block content %} <x-frame style="--width: 768px"><h1>{{ resource.title }}</h1><p>by <a href="/profile/{{ resource.author.username }}">{{ resource.author.formatted_name }}</a></p><p>{{ resource.description }}</p>{% if resource.replaced_by %}<h2>Obsolete</h2><p>This picture has been replaced by <a href="/picture/{{ resource.replaced_by.id }}">{{ resource.replaced_by.title }}</a>.</p><div id="picture-view"> <x-frame id="picture-actions"> <ul class="action-list"> <li><a href="{{ resource.origin_url }}">Original source</a></li> <li><a href="/raw/picture/{{ resource.id }}" target="_blank">View</a></li> <li><a href="/picture/{{ resource.id }}/get-annotations">Download annotations</a></li> <li><a href="/raw/picture/{{ resource.id }}" download="GigadataPicture_{{ resource.id }}{{ file_extension }}">Download</a> {% if have_permission %}</li> <li><a href="/picture/{{ resource.id }}/annotate">Annotate</a></li> <li><a href="/picture/{{ resource.id }}/put-annotations-form">Submit JSON annotations</a></li> <li><a href="/picture/{{ resource.id }}/edit-metadata">Edit title or description</a>{% endif %} {% if current_user %}</li> <li><a href="/picture/{{ resource.id }}/copy">Copy</a>{% endif %}</li> </ul> </x-frame> <x-frame style="--width: 768px"> <h1>{{ resource.title }}</h1> <p>by <a href="/profile/{{ resource.author.username }}">{{ resource.author.formatted_name }}</a></p> <p>{{ resource.description }}</p> {% if resource.replaced_by %} <h2>Obsolete</h2> <p> This picture has been replaced by <a href="/picture/{{ resource.replaced_by.id }}">{{ resource.replaced_by.title }}</a>. </p> {% if have_permission %} <form method="POST" action="/picture/{{ resource.id }}/remove-replacement"> <button class="button-flat" type="submit">Remove replacement</button> </form> {% endif %} {% endif %}{% if have_permission %} <form method="POST" action="/picture/{{ resource.id }}/remove-replacement"><button class="button-flat" type="submit">Remove replacement</button></form><details> <summary>Delete</summary> <a href="/picture/{{ resource.id }}/delete">Delete</a> </details>{% endif %} {% endif %}<p><a href="{{ resource.origin_url }}">Original source</a> |<a href="/raw/picture/{{ resource.id }}">View</a> |<a href="/picture/{{ resource.id }}/get-annotations">Download annotations</a> |<a href="/raw/picture/{{ resource.id }}" download="GigadataPicture_{{ resource.id }}{{ file_extension }}">Download</a> {% if have_permission %}|<a href="/picture/{{ resource.id }}/annotate">Annotate</a> |<a href="/picture/{{ resource.id }}/put-annotations-form">Submit JSON annotations</a> |<a href="/picture/{{ resource.id }}/edit-metadata">Edit title or description</a>{% endif %} {% if current_user %}|<a href="/picture/{{ resource.id }}/copy">Copy</a>{% endif %}</p>{% if have_permission %}<details><summary>Delete</summary><a href="/picture/{{ resource.id }}/delete">Delete</a></details>{% endif %}<x-vbox><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><x-vbox> <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 %} </ul></x-hbox>{% if current_user %}<h3>Your rating</h3><form id="rating-form" method="POST" action="/picture/{{ resource.id }}/rate"></div> <x-buttonbox><label> <input name="rating" type="radio" value="0" {% if not own_rating.rating %}checked{% endif %}>Clear rating<input type="checkbox" id="show-shapes" checked> Show shapes</label> <div class="star-rating-container"><input type="radio" id="stars-5" name="rating" value="5" title="Perfect" {% if own_rating.rating == 5 %}checked{% endif %}><label for="stars-5" tabindex="0"><iconify-icon icon="mdi:star" class="star">5 stars</iconify-icon></label><input type="radio" id="stars-4" name="rating" value="4" title="Good" {% if own_rating.rating == 4 %}checked{% endif %}><label for="stars-4" tabindex="0"><iconify-icon icon="mdi:star" class="star">4 stars</iconify-icon></label><input type="radio" id="stars-3" name="rating" value="3" title="OK" {% if own_rating.rating == 3 %}checked{% endif %}><label for="stars-3" tabindex="0"><iconify-icon icon="mdi:star" class="star">3 stars</iconify-icon></label><input type="radio" id="stars-2" name="rating" value="2" title="Poor" {% if own_rating.rating == 2 %}checked{% endif %}><label for="stars-2" tabindex="0"><iconify-icon icon="mdi:star" class="star">2 stars</iconify-icon></label><input type="radio" id="stars-1" name="rating" value="1" title="Awful" {% if own_rating.rating == 1 %}checked{% endif %}><label for="stars-1" tabindex="0"><iconify-icon icon="mdi:star" class="star">1 star</iconify-icon></label></div><button type="submit">Rate</button></form>{% endif %}<h2>Details</h2><div class="icon-explainer"><span>Type</span><span>{{ resource.nature.id }}</span><span>File format</span><span>{{ resource.file_format }}</span><span>Size</span><span>{{ size[0] }}×{{ size[1] }}</span><span>Number of regions</span><span>{{ resource.regions | length }}</span><span>Number of labelled regions</span><span>{{ resource.regions | selectattr("object_id") | list | length }}</span><span>Date uploaded</span><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:{% for licence in licences %}<a href="{{ licence.info_url }}" target="_blank">{{ licence.title }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</small><x-vbox class="picture-licence-logos">{% for licence in licences %}{% if licence.logo_url %}{% if licence.info_url %}<a href="{{ licence.info_url }}" target="_blank" tabindex="-1">{# An equivalent link already exists, only one is focusable #}<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> {% if resource.average_rating %} <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> {% else %} <p>No ratings yet.</p> {% endif %} {% 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 %}> Clear rating </label> <div class="star-rating-container"> <input type="radio" id="stars-5" name="rating" value="5" title="Perfect" {% if own_rating.rating == 5 %}checked{% endif %}> <label for="stars-5" tabindex="0"><iconify-icon icon="mdi:star" class="star">5 stars</iconify-icon></label> <input type="radio" id="stars-4" name="rating" value="4" title="Good" {% if own_rating.rating == 4 %}checked{% endif %}> <label for="stars-4" tabindex="0"><iconify-icon icon="mdi:star" class="star">4 stars</iconify-icon></label> <input type="radio" id="stars-3" name="rating" value="3" title="OK" {% if own_rating.rating == 3 %}checked{% endif %}> <label for="stars-3" tabindex="0"><iconify-icon icon="mdi:star" class="star">3 stars</iconify-icon></label> <input type="radio" id="stars-2" name="rating" value="2" title="Poor" {% if own_rating.rating == 2 %}checked{% endif %}> <label for="stars-2" tabindex="0"><iconify-icon icon="mdi:star" class="star">2 stars</iconify-icon></label> <input type="radio" id="stars-1" name="rating" value="1" title="Awful" {% if own_rating.rating == 1 %}checked{% endif %}> <label for="stars-1" tabindex="0"><iconify-icon icon="mdi:star" class="star">1 star</iconify-icon></label> </div> <button type="submit">Rate</button> </form> {% endif %} <h2>Details</h2> <div class="icon-explainer"> <span>Type</span> <span>{{ resource.nature.id }}</span> <span>File format</span> <span>{{ resource.file_format }}</span> <span>Size</span> <span>{{ size[0] }}×{{ size[1] }}</span> <span>Number of regions</span> <span>{{ resource.regions | length }}</span> <span>Number of labelled regions</span> <span>{{ resource.regions | selectattr("object_id") | list | length }}</span> <span>Date uploaded</span> <span>{{ resource.timestamp }}</span> </div> <p> Contains objects: {% for object_id in contains %} <a href="/object/{{ object_id }}">{{ object_id }}</a>{% if not loop.last %}, {% endif %}{% endfor %} </p> <h2>Licensing</h2> <x-hbox style="justify-content: space-between"> <small class="picture-licensing-info"> Available under: {% for licence in licences %} <a href="{{ licence.info_url }}" target="_blank"> {{ licence.title }} </a> {% if not loop.last %}, {% endif %} {% endfor %} </small> <x-vbox class="picture-licence-logos"> {% for licence in licences[:6] %} {% if licence.logo_url %} {% if licence.info_url %} <a href="{{ licence.info_url }}" target="_blank" tabindex="-1"> {# An equivalent link already exists, only one is focusable #} <img src="{{ licence.logo_url }}" alt="{{ licence.title }}" class="licence-logo"> </a> {% else %}<img src="{{ licence.logo_url }}" alt="{{ licence.title }}" class="licence-logo"> </a>{% else %}<img src="{{ licence.logo_url }}" alt="{{ licence.title }}" class="licence-logo">{% endif %}{% endif %} {% endfor %} {% if licences | length > 6 %} <details> <summary>More</summary> <x-vbox> {% for licence in licences[6:] %} {% if licence.logo_url %} {% if licence.info_url %} <a href="{{ licence.info_url }}" target="_blank" tabindex="-1"> {# An equivalent link already exists, only one is focusable #} <img src="{{ licence.logo_url }}" alt="{{ licence.title }}" class="licence-logo"> </a> {% else %} <img src="{{ licence.logo_url }}" alt="{{ licence.title }}" class="licence-logo"> {% endif %} {% endif %} {% endfor %} </x-vbox> </details>{% endif %} {% endfor %}</x-vbox></x-hbox><h2>Copies</h2><ul class="thumbnail-list">{% for copy in resource.copies %}<li><a href="/picture/{{ copy.id }}"><div class="annotation-zone"><img src="/raw/picture/{{ copy.id }}" alt="{{ copy.title }}"></x-vbox> </x-hbox> <h2>Copies</h2> <ul class="thumbnail-list"> {% for copy in resource.copies %} <li> <a href="/picture/{{ copy.id }}"> <div class="annotation-zone"> <img src="/raw/picture/{{ copy.id }}" alt="{{ copy.title }}">{% for region in copy.regions %}{% if region.json.type == "bbox" %}<svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"><rect x="{{ region.json.shape.x * copy.width }}"y="{{ region.json.shape.y * copy.height }}"width="{{ region.json.shape.w * copy.width }}"height="{{ region.json.shape.h * copy.height }}"fill="none" class="shape-bbox shape"></rect></svg>{% elif region.json.type == "polygon" %}<svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"><polygon points="{% for point in region.json.shape %}{{ point.x * copy.width }},{{ point.y * copy.height }} {% endfor %}" fill="none" class="shape-polygon shape"></polygon></svg>{% elif region.json.type == "polyline" %}<svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"><polyline points="{% for point in region.json.shape %}{{ point.x * copy.width }},{{ point.y * copy.height }} {% endfor %}" fill="none" class="shape-polyline shape"></polyline></svg>{% elif region.json.type == "point" %}<svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"><circle cx="{{ region.json.shape.x * copy.width }}" cy="{{ region.json.shape.y * copy.height }}" r="0" fill="none" class="shape-point shape"></circle></svg>{% endif %}{% endfor %}{% for region in copy.regions %} {% if region.json.type == "bbox" %} <svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"> <rect x="{{ region.json.shape.x * copy.width }}" y="{{ region.json.shape.y * copy.height }}" width="{{ region.json.shape.w * copy.width }}" height="{{ region.json.shape.h * copy.height }}" fill="none" class="shape-bbox shape" ></rect> </svg> {% elif region.json.type == "polygon" %} <svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"> <polygon points="{% for point in region.json.shape %}{{ point.x * copy.width }},{{ point.y * copy.height }} {% endfor %}" fill="none" class="shape-polygon shape"></polygon> </svg> {% elif region.json.type == "polyline" %} <svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"> <polyline points="{% for point in region.json.shape %}{{ point.x * copy.width }},{{ point.y * copy.height }} {% endfor %}" fill="none" class="shape-polyline shape"></polyline> </svg> {% elif region.json.type == "point" %} <svg class="shape-container" viewBox="0 0 {{ copy.width }} {{ copy.height }}"> <circle cx="{{ region.json.shape.x * copy.width }}" cy="{{ region.json.shape.y * copy.height }}" r="0" fill="none" class="shape-point shape"></circle> </svg> {% endif %} {% endfor %} </div> <div class="list-detail"> {{ copy.title }} </div> </a> <div class="list-more"> <form method="POST" action="/picture/{{ copy.id }}/mark-replacement"> <button type="submit">Designate replacement</button> </form></div> <div class="list-detail">{{ copy.title }}</div></a><div class="list-more"><form method="POST" action="/picture/{{ copy.id }}/mark-replacement"><button type="submit">Designate replacement</button></form></div></li>{% endfor %}</ul></x-vbox></x-frame></li> {% endfor %} </ul> </x-vbox> </x-frame> </div><svg width="0" height="0"> <defs> <clipPath id="star-clip">