etiquette/frontends/etiquette_flask/templates/photo.html

491 lines
14 KiB
HTML
Raw Normal View History

2016-09-18 08:33:46 +00:00
<!DOCTYPE html5>
<html>
<head>
{% import "header.html" as header %}
{% import "tag_object.html" as tag_object %}
<title>{{photo.basename}} | Photos</title>
2016-09-18 08:33:46 +00:00
<meta charset="UTF-8">
2017-07-21 06:10:48 +00:00
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/static/css/common.css">
<link rel="stylesheet" href="/static/css/etiquette.css">
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
<script src="/static/js/common.js"></script>
2018-09-23 22:52:19 +00:00
<script src="/static/js/api.js"></script>
<script src="/static/js/hotkeys.js"></script>
<script src="/static/js/photo_clipboard.js"></script>
<script src="/static/js/spinner.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
2016-11-27 09:06:11 +00:00
2016-09-18 08:33:46 +00:00
<style>
#content_body
{
--narrow:0;
2016-09-18 08:33:46 +00:00
flex: 1;
grid-template:
"left right" 1fr
/ 310px 1fr;
2016-09-18 08:33:46 +00:00
}
#left
{
grid-area: left;
2016-09-18 08:33:46 +00:00
display: flex;
flex-direction: column;
min-height: min-content;
background-color: var(--color_transparency);
2016-09-18 08:33:46 +00:00
}
#right
{
grid-area: right;
display: grid;
position: relative;
grid-template: "viewer" 1fr / 1fr;
2016-09-18 08:33:46 +00:00
}
#editor_area
{
padding: 8px;
word-wrap: break-word;
2016-09-18 08:33:46 +00:00
}
#before_after_links
{
width: max-content;
margin: auto;
}
2016-09-18 08:33:46 +00:00
#message_area
{
display: flex;
min-height: 30px;
max-height: none;
margin: 8px;
2016-09-18 08:33:46 +00:00
}
#photo_viewer
2016-09-18 08:33:46 +00:00
{
grid-area: viewer;
display: grid;
2016-09-18 08:33:46 +00:00
}
.photo_viewer_audio,
.photo_viewer_video,
.photo_viewer_application,
.photo_viewer_text
2016-09-18 08:33:46 +00:00
{
justify-items: center;
2016-10-30 01:46:23 +00:00
align-items: center;
}
#photo_viewer audio,
#photo_viewer video
2016-10-30 01:46:23 +00:00
{
2017-03-31 02:34:39 +00:00
width: 100%;
2016-10-10 03:50:13 +00:00
max-width: 100%;
max-height: 100%;
2016-09-18 08:33:46 +00:00
}
.photo_viewer_image
2016-09-18 08:33:46 +00:00
{
display: grid;
justify-items: center;
align-items: center;
max-height: 100%;
background-repeat: no-repeat;
2016-09-18 08:33:46 +00:00
}
.photo_viewer_image img
2016-09-18 08:33:46 +00:00
{
max-height: 100%;
max-width: 100%;
2016-09-18 08:33:46 +00:00
}
#photo_viewer a
{
margin: auto;
}
#hovering_tools
{
position: absolute;
right: 8px;
top: 8px;
display: flex;
flex-direction: column;
align-items: flex-end;
/*background-color: var(--color_primary);*/
}
@media screen and (max-width: 800px)
{
#content_body
{
--narrow:1;
grid-template:
"right" 1fr
"left" 1fr;
}
#left
{
/*
Display: none will be returned back to flex as soon as the page detects
that the screen is in narrow mode and turns off the tag box's autofocus.
*/
display: none;
width: initial;
max-width: none;
}
#right
{
height: calc(100vh - 40px);
}
#message_area
{
flex: 2;
height: initial;
max-height: none;
}
}
2016-09-18 08:33:46 +00:00
</style>
2016-11-27 09:06:11 +00:00
</head>
2016-09-18 08:33:46 +00:00
<body>
{{header.make_header(session=session)}}
2016-09-18 08:33:46 +00:00
<div id="content_body">
<div id="left">
<div id="editor_area">
<h3>{{photo.basename}}</h3>
2016-09-18 08:33:46 +00:00
<!-- TAG INFO -->
<h4>Tags</h4>
<ul id="this_tags">
<li>
<input id="add_tag_textbox" type="text" list="tag_autocomplete_datalist" autofocus>
<button id="add_tag_button" class="green_button" onclick="add_photo_tag_form();">add</button>
</li>
{% set tags = photo.get_tags()|sort_tags %}
{% for tag in tags %}
2016-09-18 08:33:46 +00:00
<li>
{{tag_object.tag_object(tag, with_alt_description=True)}}<!--
--><button
class="remove_tag_button red_button"
onclick="remove_photo_tag_form('{{photo.id}}', '{{tag.name}}');">
</button>
2016-09-18 08:33:46 +00:00
</li>
{% endfor %}
</ul>
<!-- METADATA & DOWNLOAD -->
<h4>
File info
</h4>
2016-09-18 08:33:46 +00:00
<ul id="metadata">
{% set author = photo.get_author() %}
{% if author is not none %}
<li>Author: <a href="/user/{{author.username}}">{{author.display_name}}</a></li>
{% endif %}
{% if photo.width %}
<li title="{{photo.area}} px">Dimensions: {{photo.width}}x{{photo.height}} px</li>
<li>Aspect ratio: {{photo.ratio}}</li>
{% endif %}
<li>Size: {{photo.bytes|bytestring}}</li>
{% if photo.duration %}
<li>Duration: {{photo.duration_string}}</li>
<li>Overall Bitrate: {{photo.bitrate|int}} kbps</li>
{% endif %}
<li><a href="{{photo|file_link}}?download=true&original_filename=true">Download as original filename</a></li>
<li><a href="{{photo|file_link}}?download=true">Download as {{photo.id}}.{{photo.extension}}</a></li>
<li><button id="refresh_metadata_button" class="green_button button_with_spinner" onclick="refresh_metadata_form();">refresh</button></li>
<li><label class="photo_card" data-id="{{photo.id}}"><input type="checkbox" class="photo_card_selector_checkbox" onclick="photo_clipboard.on_photo_select(event)"/>Clipboard</label></li>
2016-09-18 08:33:46 +00:00
</ul>
2016-09-18 08:33:46 +00:00
<!-- CONTAINING ALBUMS -->
{% set albums = photo.get_containing_albums() %}
{% if albums %}
2016-09-18 08:33:46 +00:00
<h4>Albums containing this photo</h4>
<ul id="containing albums">
{% for album in albums %}
<li><a href="/album/{{album.id}}">{{album.display_name}}</a></li>
2016-09-18 08:33:46 +00:00
{% endfor %}
</ul>
{% endif %}
<!-- BEFORE & AFTER SEARCH LINKS -->
<div id="before_after_links">
<a href="/search?created=-{{photo.created}}">&larr;Before</a>
<span> | </span>
<a href="/search?created={{photo.created}}-&orderby=created-asc">After&rarr;</a>
</div>
2016-09-18 08:33:46 +00:00
</div>
<div id="message_area"></div>
2016-09-18 08:33:46 +00:00
</div>
<div id="right">
2016-10-30 01:46:23 +00:00
<!-- THE PHOTO ITSELF -->
<div id="photo_viewer" class="photo_viewer_{{photo.simple_mimetype}}" {%if photo.simple_mimetype == "image"%}onclick="toggle_hoverzoom(event)"{%endif%}>
{% if photo.simple_mimetype == "image" %}
<img src="{{photo|file_link}}" alt="{{photo.basename}}" onload="this.style.opacity=0.99">
{% elif photo.simple_mimetype == "video" %}
<video
src="{{photo|file_link}}"
controls
preload=none
{%if photo.thumbnail%}poster="/thumbnail/{{photo.id}}.jpg"{%endif%}
></video>
{% elif photo.simple_mimetype == "audio" %}
<audio src="{{photo|file_link}}" controls></audio>
2016-09-18 08:33:46 +00:00
{% else %}
<a href="{{photo|file_link}}">View {{photo.basename}}</a>
2016-09-18 08:33:46 +00:00
{% endif %}
</div>
<div id="hovering_tools">
{% if photo.simple_mimetype == "video" %}
<button id="generate_thumbnail_button" class="green_button button_with_spinner" onclick="generate_thumbnail_for_video(event)">Capture thumbnail</button>
{% endif %}
<button
class="red_button button_with_confirm"
data-onclick="api.photos.delete(PHOTO_ID, false, api.photos.callback_go_to_search)"
data-prompt="Delete photo, keep file?"
data-cancel-class="gray_button"
>
Remove
</button>
<button
class="red_button button_with_confirm"
data-onclick="api.photos.delete(PHOTO_ID, true, api.photos.callback_go_to_search)"
data-prompt="Delete file on disk?"
data-cancel-class="gray_button"
>
Delete
</button>
</div>
2016-09-18 08:33:46 +00:00
</div>
</div>
</body>
2016-11-27 09:06:11 +00:00
2016-09-18 08:33:46 +00:00
<script type="text/javascript">
var PHOTO_ID = "{{photo.id}}";
2016-10-10 03:50:13 +00:00
var add_tag_box = document.getElementById('add_tag_textbox');
var add_tag_button = document.getElementById('add_tag_button');
2018-07-23 02:12:08 +00:00
add_tag_box.addEventListener("keyup", common.entry_with_history_hook);
add_tag_box.addEventListener("keyup", tag_autocomplete.entry_with_tagname_replacements_hook);
2018-07-23 02:12:08 +00:00
common.bind_box_to_button(add_tag_box, add_tag_button, false);
2016-09-18 08:33:46 +00:00
var message_area = document.getElementById('message_area');
function add_photo_tag_form()
{
let tagname = document.getElementById("add_tag_textbox").value;
if (tagname == "")
{
return;
}
api.photos.add_tag(PHOTO_ID, tagname, add_remove_photo_tag_callback);
add_tag_box.value = "";
}
function remove_photo_tag_form(photo_id, tagname)
{
api.photos.remove_tag(photo_id, tagname, add_remove_photo_tag_callback);
}
function add_remove_photo_tag_callback(response)
{
let message_text;
let message_positivity;
let tagname = response.data.tagname;
if ("error_type" in response.data)
{
message_positivity = "message_negative";
message_text = response.data.error_message;
}
else
{
let action;
message_positivity = "message_positive";
if (response.meta.request_url.includes("add_tag"))
{
message_text = "Added tag " + tagname;
}
else if (response.meta.request_url.includes("remove_tag"))
{
message_text = "Removed tag " + tagname;
}
else
{
return;
}
}
2018-07-23 02:12:08 +00:00
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
}
function generate_thumbnail_callback(response)
{
if (response.meta.status == 200)
{
common.create_message_bubble(message_area, "message_positive", "Thumbnail captured", 8000);
}
else
{
common.create_message_bubble(message_area, "message_negative", response.data.error_message, 8000);
}
generate_thumbnail_button = document.getElementById("generate_thumbnail_button");
window[generate_thumbnail_button.dataset.spinnerCloser]();
}
function generate_thumbnail_for_video(event)
{
let timestamp = document.querySelector("#right video").currentTime;
let special = {"timestamp": timestamp};
api.photos.generate_thumbnail(PHOTO_ID, special, generate_thumbnail_callback)
}
function refresh_metadata_form()
{
api.photos.refresh_metadata(PHOTO_ID, common.refresh);
}
var ZOOM_BG_URL = "url('{{photo|file_link}}')";
function enable_hoverzoom(event)
2016-10-30 01:46:23 +00:00
{
//console.log("enable zoom");
let photo_viewer = document.getElementById("photo_viewer");
let photo_img = photo_viewer.children[0];
if (
photo_img.naturalWidth < photo_viewer.offsetWidth &&
photo_img.naturalHeight < photo_viewer.offsetHeight
)
2016-10-30 01:46:23 +00:00
{
return;
}
photo_img.style.opacity = "0";
photo_img.style.display = "none";
photo_viewer.style.cursor = "zoom-out";
photo_viewer.style.backgroundImage = ZOOM_BG_URL;
photo_viewer.onmousemove = move_hoverzoom;
move_hoverzoom(event)
2016-10-30 01:46:23 +00:00
return true;
}
function disable_hoverzoom()
{
//console.log("disable zoom");
let photo_viewer = document.getElementById("photo_viewer");
let photo_img = photo_viewer.children[0];
photo_img.style.opacity = "100";
photo_viewer.style.cursor = "";
photo_img.style.display = "";
photo_viewer.style.backgroundImage = "none";
photo_viewer.onmousemove = null;
2016-10-30 01:46:23 +00:00
}
function toggle_hoverzoom(event)
2016-10-30 01:46:23 +00:00
{
let photo_img = document.getElementById("photo_viewer").children[0];
if (photo_img.style.opacity === "0")
2016-10-30 01:46:23 +00:00
{
disable_hoverzoom();
}
else
{
enable_hoverzoom(event);
2016-10-30 01:46:23 +00:00
}
let content_body = document.getElementById('content_body');
if (getComputedStyle(content_body).getPropertyValue("--narrow") == 0)
{
add_tag_box.focus();
}
2016-10-30 01:46:23 +00:00
}
2016-11-27 09:06:11 +00:00
2016-10-30 01:46:23 +00:00
function move_hoverzoom(event)
{
let photo_viewer = document.getElementById("photo_viewer");
let photo_img = photo_viewer.children[0];
let x;
let y;
/*
When clicking on the image, the event handler takes the image as the event
target even though the handler was assigned to the holder. The coordinates
for the zoom need to be based on the holder, so when this happens we need
to adjust the numbers.
I'm not sure why the offset is the holder's offsetLeft. It seems that when
the event triggers on the holder, the event X is based on its bounding box,
but when it triggers on the image it's based on the viewport.
*/
let mouse_x = event.offsetX;
let mouse_y = event.offsetY;
if (event.target !== photo_viewer)
{
mouse_x -= photo_viewer.offsetLeft;
mouse_y -= photo_viewer.offsetTop;
}
/*
Adding 5% to perceived position gives us a bit of padding around the image,
so you don't need to navigate a 1px line to see the edge.
We first subtract half of the image dimensions so that the 5% is applied
to both left and right. Otherwise 105% of 0 is still 0 which doesn't
apply padding on the left.
*/
mouse_x -= (photo_viewer.offsetWidth / 2);
2016-11-27 09:06:11 +00:00
mouse_x *= 1.05;
mouse_x += (photo_viewer.offsetWidth / 2);
mouse_y -= (photo_viewer.offsetHeight / 2);
2016-11-27 09:06:11 +00:00
mouse_y *= 1.05;
mouse_y += (photo_viewer.offsetHeight / 2);
if (photo_img.naturalWidth < photo_viewer.offsetWidth)
2016-10-30 01:46:23 +00:00
{
// If the image is smaller than the frame, just center it
x = (photo_img.naturalWidth - photo_viewer.offsetWidth) / 2;
2016-10-30 01:46:23 +00:00
}
else
{
// Take the amount of movement necessary (frame width - image width)
// times our distance across the image as a percentage.
x = (photo_img.naturalWidth - photo_viewer.offsetWidth) * (mouse_x / photo_viewer.offsetWidth);
2016-10-30 01:46:23 +00:00
}
if (photo_img.naturalHeight < photo_viewer.offsetHeight)
2016-10-30 01:46:23 +00:00
{
y = (photo_img.naturalHeight - photo_viewer.offsetHeight) / 2;
2016-10-30 01:46:23 +00:00
}
else
{
y = (photo_img.naturalHeight - photo_viewer.offsetHeight) * (mouse_y / photo_viewer.offsetHeight);
2016-10-30 01:46:23 +00:00
}
//console.log(x);
photo_viewer.style.backgroundPosition=(-x)+"px "+(-y)+"px";
2016-10-30 01:46:23 +00:00
}
tag_autocomplete.init_datalist();
setTimeout(
/*
When the screen is in column mode, the autofocusing of the tag box snaps the
screen down to it, which is annoying. By starting the #left hidden, we have
an opportunity to unset the autofocus before showing it.
*/
function()
{
let content_body = document.getElementById("content_body");
if (getComputedStyle(content_body).getPropertyValue("--narrow") == 1)
{
add_tag_box.autofocus = false;
}
document.getElementById("left").style.display = "flex";
},
0
);
2016-11-27 09:06:11 +00:00
</script>
</html>