443 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html5>
 | 
						|
<html>
 | 
						|
<head>
 | 
						|
    {% import "header.html" as header %}
 | 
						|
    {% import "tag_object.html" as tag_object %}
 | 
						|
    <title>Photo {{photo.basename}}</title>
 | 
						|
    <meta charset="UTF-8">
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 | 
						|
    <link rel="stylesheet" href="/static/common.css">
 | 
						|
    <script src="/static/common.js"></script>
 | 
						|
    {% set filename = photo.id + photo.dot_extension %}
 | 
						|
    {% set file_link = "/file/" + filename %}
 | 
						|
 | 
						|
<style>
 | 
						|
#content_body
 | 
						|
{
 | 
						|
    /* Override common.css */
 | 
						|
    flex: 1;
 | 
						|
    flex-direction: row;
 | 
						|
}
 | 
						|
#left
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    flex-direction: column;
 | 
						|
}
 | 
						|
#right
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    flex: 1;
 | 
						|
}
 | 
						|
#editor_holder
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    flex-direction: column;
 | 
						|
 | 
						|
    max-width: 300px;
 | 
						|
    /*padding: 8px;*/
 | 
						|
 | 
						|
}
 | 
						|
#editor_area
 | 
						|
{
 | 
						|
    flex: 3;
 | 
						|
    padding: 8px;
 | 
						|
 | 
						|
    background-color: rgba(0, 0, 0, 0.1);
 | 
						|
 | 
						|
    word-wrap: break-word;
 | 
						|
}
 | 
						|
#message_area_bg
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    flex: 0 1 100%;
 | 
						|
 | 
						|
    height: 100%;
 | 
						|
    min-height: 30px;
 | 
						|
    padding: 8px;
 | 
						|
 | 
						|
    background-color: rgba(0, 0, 0, 0.1);
 | 
						|
}
 | 
						|
#message_area
 | 
						|
{
 | 
						|
    flex: auto;
 | 
						|
 | 
						|
    max-height: none;
 | 
						|
}
 | 
						|
.photo_viewer
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    flex: 1;
 | 
						|
    flex-direction: column;
 | 
						|
    justify-content: center;
 | 
						|
    align-items: center;
 | 
						|
}
 | 
						|
.photo_viewer a
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    justify-content: center;
 | 
						|
    align-items: center;
 | 
						|
}
 | 
						|
#photo_img_holder
 | 
						|
{
 | 
						|
    display: flex;
 | 
						|
    justify-content: center;
 | 
						|
    align-items: center;
 | 
						|
 | 
						|
    height: 100%;
 | 
						|
    width: 100%;
 | 
						|
 | 
						|
    background-repeat: no-repeat;
 | 
						|
}
 | 
						|
#photo_img_holder img
 | 
						|
{
 | 
						|
    max-height: 100%;
 | 
						|
    max-width: 100%;
 | 
						|
}
 | 
						|
.photo_viewer audio
 | 
						|
{
 | 
						|
    width: 100%;
 | 
						|
}
 | 
						|
.photo_viewer video
 | 
						|
{
 | 
						|
    max-width: 100%;
 | 
						|
    max-height: 100%;
 | 
						|
    width: 100%;
 | 
						|
    overflow: hidden;
 | 
						|
}
 | 
						|
#refresh_metadata_button
 | 
						|
{
 | 
						|
    font-size: 11px;
 | 
						|
}
 | 
						|
@media screen and (max-width: 800px)
 | 
						|
{
 | 
						|
    #content_body
 | 
						|
    {
 | 
						|
        /*
 | 
						|
        When flexing, it tries to contain itself entirely in the screen,
 | 
						|
        forcing #left and #right to squish together.
 | 
						|
        */
 | 
						|
        flex: none;
 | 
						|
        flex-direction: column-reverse;
 | 
						|
    }
 | 
						|
    #left
 | 
						|
    {
 | 
						|
        /*
 | 
						|
        Display: None will be overridden 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;
 | 
						|
        margin-top: 8px;
 | 
						|
    }
 | 
						|
    #right
 | 
						|
    {
 | 
						|
        flex: none;
 | 
						|
        height: calc(100% - 20px);
 | 
						|
    }
 | 
						|
    #editor_holder
 | 
						|
    {
 | 
						|
        display: flex;
 | 
						|
        flex-direction: row;
 | 
						|
        max-width: none;
 | 
						|
    }
 | 
						|
    #message_area
 | 
						|
    {
 | 
						|
        flex: 2;
 | 
						|
        height: initial;
 | 
						|
        max-height: none;
 | 
						|
    }
 | 
						|
}
 | 
						|
</style>
 | 
						|
</head>
 | 
						|
 | 
						|
 | 
						|
<body>
 | 
						|
{{header.make_header(session=session)}}
 | 
						|
<div id="content_body">
 | 
						|
<div id="left">
 | 
						|
    <div id="editor_holder">
 | 
						|
    <div id="editor_area">
 | 
						|
        <!-- TAG INFO -->
 | 
						|
        <h4>Tags</h4>
 | 
						|
        <ul id="this_tags">
 | 
						|
            <li>
 | 
						|
                <input id="add_tag_textbox" type="text" autofocus>
 | 
						|
                <button id="add_tag_button" class="add_tag_button" onclick="submit_tag(receive_callback);">add</button>
 | 
						|
            </li>
 | 
						|
            {% set tags = photo.sorted_tags() %}
 | 
						|
            {% for tag in tags %}
 | 
						|
            <li>
 | 
						|
                {{tag_object.tag_object(tag, qualified_name=True, max_len=30, with_alt_description=True, with_alt_qualified_name=True)}}<!--
 | 
						|
                --><button
 | 
						|
                class="remove_tag_button"
 | 
						|
                onclick="remove_photo_tag('{{photo.id}}', '{{tag.name}}', receive_callback);">
 | 
						|
                </button>
 | 
						|
            </li>
 | 
						|
            {% endfor %}
 | 
						|
        </ul>
 | 
						|
 | 
						|
        <!-- METADATA & DOWNLOAD -->
 | 
						|
        <h4>
 | 
						|
            File info
 | 
						|
            <button id="refresh_metadata_button" class="add_tag_button" onclick="refresh_metadata('{{photo.id}}');">refresh</button>
 | 
						|
        </h4>
 | 
						|
        <ul id="metadata">
 | 
						|
        <li>Filename: {{photo.basename}}</li>
 | 
						|
        {% set author = photo.author %}
 | 
						|
        {% if author is not none %}
 | 
						|
            <li>Author: <a href="/user/{{author.username}}">{{author.username}}</a></li>
 | 
						|
        {% endif %}
 | 
						|
        {% if photo.width %}
 | 
						|
            <li>Dimensions: {{photo.width}}x{{photo.height}} px</li>
 | 
						|
            <li>Aspect ratio: {{photo.ratio}}</li>
 | 
						|
        {% endif %}
 | 
						|
        <li>Size: {{photo.bytestring()}}</li>
 | 
						|
        {% if photo.duration %}
 | 
						|
            <li>Duration: {{photo.duration_string}}</li>
 | 
						|
            <li>Overall Bitrate: {{photo.bitrate|int}} kbps</li>
 | 
						|
        {% endif %}
 | 
						|
        <li><a href="/file/{{photo.id}}{{photo.dot_extension}}?download=true&original_filename=true">Download as original filename</a></li>
 | 
						|
        <li><a href="/file/{{photo.id}}{{photo.dot_extension}}?download=true">Download as {{photo.id}}.{{photo.extension}}</a></li>
 | 
						|
        </ul>
 | 
						|
 | 
						|
        <!-- CONTAINING ALBUMS -->
 | 
						|
        {% set albums = photo.albums() %}
 | 
						|
        {% if albums %}
 | 
						|
        <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>
 | 
						|
            {% endfor %}
 | 
						|
        {% endif %}
 | 
						|
        </ul>
 | 
						|
    </div>
 | 
						|
    <div id="message_area_bg">
 | 
						|
        <div id="message_area">
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
    </div>
 | 
						|
</div>
 | 
						|
 | 
						|
<div id="right">
 | 
						|
    <!-- THE PHOTO ITSELF -->
 | 
						|
    <div class="photo_viewer">
 | 
						|
        {% if photo.simple_mimetype == "image" %}
 | 
						|
        <div id="photo_img_holder" onclick="toggle_hoverzoom(event)">
 | 
						|
            <img
 | 
						|
            id="photo_img"
 | 
						|
            src="{{file_link}}"
 | 
						|
            alt="{{photo.basename}}"
 | 
						|
            onload="this.style.opacity=0.99"
 | 
						|
            >
 | 
						|
        </div>
 | 
						|
        {% elif photo.simple_mimetype == "video" %}
 | 
						|
        <video src="{{file_link}}" controls preload=none {%if photo.thumbnail%}poster="/thumbnail/{{photo.id}}.jpg"{%endif%}></video>
 | 
						|
        {% elif photo.simple_mimetype == "audio" %}
 | 
						|
        <audio src="{{file_link}}" controls></audio>
 | 
						|
        {% else %}
 | 
						|
        <a href="{{file_link}}">View {{filename}}</a>
 | 
						|
        {% endif %}
 | 
						|
    </div>
 | 
						|
</div>
 | 
						|
</div>
 | 
						|
</body>
 | 
						|
 | 
						|
 | 
						|
<script type="text/javascript">
 | 
						|
var content_body = document.getElementById('content_body');
 | 
						|
var add_tag_box = document.getElementById('add_tag_textbox');
 | 
						|
var add_tag_button = document.getElementById('add_tag_button');
 | 
						|
var message_area = document.getElementById('message_area');
 | 
						|
add_tag_box.onkeydown = function(){entry_with_history_hook(add_tag_box, add_tag_button)};
 | 
						|
 | 
						|
photo_img_holder = document.getElementById("photo_img_holder");
 | 
						|
photo_img = document.getElementById("photo_img");
 | 
						|
 | 
						|
function add_photo_tag(photoid, tagname, callback)
 | 
						|
{
 | 
						|
    if (tagname === ""){return}
 | 
						|
    var url = "/photo/" + photoid + "/add_tag";
 | 
						|
    var data = new FormData();
 | 
						|
    data.append("tagname", tagname);
 | 
						|
    return post(url, data, callback);
 | 
						|
}
 | 
						|
function remove_photo_tag(photoid, tagname, callback)
 | 
						|
{
 | 
						|
    if (tagname === ""){return}
 | 
						|
    var url = "/photo/" + photoid + "/remove_tag";
 | 
						|
    var data = new FormData();
 | 
						|
    data.append("tagname", tagname);
 | 
						|
    return post(url, data, callback);
 | 
						|
}
 | 
						|
function submit_tag(callback)
 | 
						|
{
 | 
						|
    add_photo_tag('{{photo.id}}', add_tag_box.value, callback);
 | 
						|
    add_tag_box.value = "";
 | 
						|
}
 | 
						|
function receive_callback(response)
 | 
						|
{
 | 
						|
    var message_text;
 | 
						|
    var message_positivity;
 | 
						|
    var tagname = response["tagname"];
 | 
						|
    if ("error_type" in response)
 | 
						|
    {
 | 
						|
        message_positivity = "message_negative";
 | 
						|
        message_text = response["error_message"];
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        var action;
 | 
						|
        message_positivity = "message_positive";
 | 
						|
        if (response["_request_url"].includes("add_tag"))
 | 
						|
        {
 | 
						|
            message_text = "Added tag " + tagname;
 | 
						|
        }
 | 
						|
        else if (response["_request_url"].includes("remove_tag"))
 | 
						|
        {
 | 
						|
            message_text = "Removed tag " + tagname;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    create_message_bubble(message_area, message_positivity, message_text, 8000);
 | 
						|
}
 | 
						|
 | 
						|
function refresh_metadata(photoid)
 | 
						|
{
 | 
						|
    var url= "/photo/" + photoid + "/refresh_metadata";
 | 
						|
    var data = new FormData();
 | 
						|
    callback = function(){location.reload();};
 | 
						|
    post(url, data, callback);
 | 
						|
}
 | 
						|
 | 
						|
function enable_hoverzoom(event)
 | 
						|
{
 | 
						|
    //console.log("enable zoom");
 | 
						|
    photo_img_holder = document.getElementById("photo_img_holder");
 | 
						|
    photo_img = document.getElementById("photo_img");
 | 
						|
    if (
 | 
						|
        photo_img.naturalWidth < photo_img_holder.offsetWidth &&
 | 
						|
        photo_img.naturalHeight < photo_img_holder.offsetHeight
 | 
						|
    )
 | 
						|
    {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    photo_img.style.opacity = "0";
 | 
						|
    photo_img.style.display = "none";
 | 
						|
    photo_img_holder.style.cursor = "zoom-out";
 | 
						|
    photo_img_holder.style.backgroundImage = "url('{{file_link}}')";
 | 
						|
    photo_img_holder.onmousemove = move_hoverzoom;
 | 
						|
    move_hoverzoom(event)
 | 
						|
    //setTimeout(function(){img_holder.onclick = toggle_hoverzoom;}, 100);
 | 
						|
    return true;
 | 
						|
}
 | 
						|
function disable_hoverzoom()
 | 
						|
{
 | 
						|
    //console.log("disable zoom");
 | 
						|
    photo_img.style.opacity = "100";
 | 
						|
    photo_img_holder.style.cursor = "";
 | 
						|
    photo_img.style.display = "";
 | 
						|
    photo_img_holder.style.backgroundImage = "none";
 | 
						|
    photo_img_holder.onmousemove = null;
 | 
						|
    //photo_img_holder.onclick = null;
 | 
						|
}
 | 
						|
function toggle_hoverzoom()
 | 
						|
{
 | 
						|
    if (photo_img.style.opacity === "0")
 | 
						|
    {
 | 
						|
        disable_hoverzoom();
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        enable_hoverzoom(event);
 | 
						|
    }
 | 
						|
    if (getComputedStyle(content_body).flexDirection != "column-reverse")
 | 
						|
    {
 | 
						|
        add_tag_box.focus();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function move_hoverzoom(event)
 | 
						|
{
 | 
						|
    var x;
 | 
						|
    var 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.
 | 
						|
    */
 | 
						|
    var mouse_x = event.offsetX;
 | 
						|
    var mouse_y = event.offsetY;
 | 
						|
    if (event.target !== photo_img_holder)
 | 
						|
    {
 | 
						|
        mouse_x -= photo_img_holder.offsetLeft;
 | 
						|
        mouse_y -= photo_img_holder.offsetTop;
 | 
						|
    }
 | 
						|
    //console.log(mouse_x);
 | 
						|
 | 
						|
    /*
 | 
						|
    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_img_holder.offsetWidth / 2);
 | 
						|
    mouse_x *= 1.05;
 | 
						|
    mouse_x += (photo_img_holder.offsetWidth / 2);
 | 
						|
 | 
						|
    mouse_y -= (photo_img_holder.offsetHeight / 2);
 | 
						|
    mouse_y *= 1.05;
 | 
						|
    mouse_y += (photo_img_holder.offsetHeight / 2);
 | 
						|
 | 
						|
    if (photo_img.naturalWidth < photo_img_holder.offsetWidth)
 | 
						|
    {
 | 
						|
        // If the image is smaller than the frame, just center it
 | 
						|
        x = (photo_img.naturalWidth - photo_img_holder.offsetWidth) / 2;
 | 
						|
    }
 | 
						|
    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_img_holder.offsetWidth) * (mouse_x / photo_img_holder.offsetWidth);
 | 
						|
    }
 | 
						|
 | 
						|
    if (photo_img.naturalHeight < photo_img_holder.offsetHeight)
 | 
						|
    {
 | 
						|
        y = (photo_img.naturalHeight - photo_img_holder.offsetHeight) / 2;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        y = (photo_img.naturalHeight - photo_img_holder.offsetHeight) * (mouse_y / photo_img_holder.offsetHeight);
 | 
						|
    }
 | 
						|
    //console.log(x);
 | 
						|
    photo_img_holder.style.backgroundPosition=(-x)+"px "+(-y)+"px";
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
    {
 | 
						|
        var left = document.getElementById("left");
 | 
						|
        if (getComputedStyle(content_body).flexDirection == "column-reverse")
 | 
						|
        {
 | 
						|
            add_tag_box.autofocus = false;
 | 
						|
        }
 | 
						|
        left.style.display = "flex";
 | 
						|
    },
 | 
						|
    0
 | 
						|
);
 | 
						|
</script>
 | 
						|
</html>
 |