656 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			656 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <html>
 | |
| <head>
 | |
|     {% import "header.html" as header %}
 | |
|     {% import "cards.html" as cards %}
 | |
|     <title>{{photo.basename}} | Photos</title>
 | |
|     <meta charset="UTF-8">
 | |
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 | |
|     <link rel="icon" href="/favicon.png" type="image/png"/>
 | |
|     <link rel="stylesheet" href="/static/css/common.css">
 | |
|     <link rel="stylesheet" href="/static/css/etiquette.css">
 | |
|     <link rel="stylesheet" href="/static/css/cards.css">
 | |
|     {% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
 | |
|     <script src="/static/js/common.js"></script>
 | |
|     <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/spinners.js"></script>
 | |
|     <script src="/static/js/tag_autocomplete.js"></script>
 | |
| 
 | |
| <style>
 | |
| #content_body
 | |
| {
 | |
|     flex: 1;
 | |
| }
 | |
| #left
 | |
| {
 | |
|     display: grid;
 | |
|     grid-template:
 | |
|         "editor_area" auto
 | |
|         "message_area" 1fr
 | |
|         /1fr;
 | |
| 
 | |
|     min-height: min-content;
 | |
| }
 | |
| #editor_area
 | |
| {
 | |
|     grid-area: editor_area;
 | |
|     word-break: break-word;
 | |
| }
 | |
| #before_after_links
 | |
| {
 | |
|     width: max-content;
 | |
|     margin: auto;
 | |
| }
 | |
| #message_area
 | |
| {
 | |
|     grid-area: message_area;
 | |
|     min-height: 30px;
 | |
|     margin-top: 8px;
 | |
| }
 | |
| #photo_viewer
 | |
| {
 | |
|     position: absolute;
 | |
|     top: 0; bottom: 0; left: 0; right: 0;
 | |
| }
 | |
| .photo_viewer_audio,
 | |
| .photo_viewer_video,
 | |
| .photo_viewer_application,
 | |
| .photo_viewer_text
 | |
| {
 | |
|     justify-items: center;
 | |
|     align-items: center;
 | |
| }
 | |
| #photo_viewer audio,
 | |
| #photo_viewer video
 | |
| {
 | |
|     width: 100%;
 | |
|     max-width: 100%;
 | |
|     max-height: 100%;
 | |
| 
 | |
|     position: absolute;
 | |
|     top: 0;
 | |
|     bottom: 0;
 | |
|     margin: auto auto;
 | |
| }
 | |
| .photo_viewer_image
 | |
| {
 | |
|     display: grid;
 | |
|     justify-items: center;
 | |
|     align-items: center;
 | |
| 
 | |
|     max-height: 100%;
 | |
|     background-repeat: no-repeat;
 | |
| }
 | |
| #photo_viewer img
 | |
| {
 | |
|     position: absolute;
 | |
|     max-height: 100%;
 | |
|     max-width: 100%;
 | |
| }
 | |
| #photo_viewer a
 | |
| {
 | |
|     margin: auto;
 | |
| }
 | |
| 
 | |
| #hovering_tools
 | |
| {
 | |
|     position: absolute;
 | |
|     right: 0px;
 | |
|     top: 0px;
 | |
| 
 | |
|     display: flex;
 | |
|     flex-direction: column;
 | |
|     align-items: flex-end;
 | |
| }
 | |
| 
 | |
| @media screen and (min-width: 800px)
 | |
| {
 | |
|     #content_body
 | |
|     {
 | |
|         grid-template:
 | |
|             "left right" 1fr
 | |
|             / 310px 1fr;
 | |
|     }
 | |
|     #right
 | |
|     {
 | |
|         position: fixed;
 | |
|         /* header=18 + 8px body top margin + 8px header/body gap = 34 */
 | |
|         top: 34px;
 | |
|         bottom: 8px;
 | |
|         right: 8px;
 | |
|         /* left=310px + 8px body left margin + 8px left/right gap = 326 */
 | |
|         left: 326px;
 | |
|     }
 | |
| }
 | |
| 
 | |
| @media screen and (max-width: 800px)
 | |
| {
 | |
|     #content_body
 | |
|     {
 | |
|         grid-template:
 | |
|             "right" 95vh
 | |
|             "left" max-content
 | |
|             / 1fr;
 | |
|     }
 | |
|     #right
 | |
|     {
 | |
|         position: absolute;
 | |
|         top: 34px;
 | |
|         bottom: 8px;
 | |
|         left: 8px;
 | |
|         right: 8px;
 | |
|     }
 | |
| }
 | |
| </style>
 | |
| </head>
 | |
| 
 | |
| <body>
 | |
| {{header.make_header(session=session)}}
 | |
| <div id="content_body">
 | |
| <div id="left" class="panel">
 | |
|     <div id="editor_area">
 | |
|         <h3 id="photo_filename">{{photo.basename}}</h3>
 | |
| 
 | |
|         <!-- TAG INFO -->
 | |
|         <h4>Tags</h4>
 | |
|         <ul id="this_tags">
 | |
|             <li>
 | |
|                 <input type="text" id="add_tag_textbox" class="entry_with_history entry_with_tagname_replacements" list="tag_autocomplete_datalist">
 | |
|                 <button id="add_tag_button" class="green_button" onclick="return add_photo_tag_form();">add</button>
 | |
|             </li>
 | |
|             {% set tags = photo.get_tags()|sort(attribute='name') %}
 | |
|             {% for tag in tags %}
 | |
|             <li>
 | |
|                 {{cards.create_tag_card(tag, link="info", with_alt_description=True)}}<!--
 | |
|                 --><button
 | |
|                 class="remove_tag_button red_button"
 | |
|                 onclick="return remove_photo_tag_form('{{photo.id}}', '{{tag.name}}');">
 | |
|                 </button>
 | |
|             </li>
 | |
|             {% endfor %}
 | |
|         </ul>
 | |
| 
 | |
|         <!-- METADATA & DOWNLOAD -->
 | |
|         <h4>
 | |
|             File info
 | |
|         </h4>
 | |
|         <ul id="metadata">
 | |
|             {% set author = photo.author %}
 | |
|             {% if author is not none %}
 | |
|                 <li>Author: <a href="/userid/{{author.id}}">{{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>Created {{photo.created|timestamp_to_naturaldate}}</li>
 | |
|             <li><button id="refresh_metadata_button" class="green_button button_with_spinner" onclick="return refresh_metadata_form();">Refresh metadata</button></li>
 | |
|             {% if request.is_localhost %}
 | |
|             <li><button id="show_in_folder_button" onclick="return show_in_folder_form();">Show in folder</button></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>
 | |
|                 <label>
 | |
|                 <input id="searchhidden_checkbox" type="checkbox" {%if photo.searchhidden%}checked{%endif%} onchange="return set_searchhidden_form();"
 | |
|                 />Hidden from search
 | |
|                 </label>
 | |
|             </li>
 | |
|             <li>
 | |
|                 <label>
 | |
|                 <input id="clipboard_checkbox" type="checkbox" class="photo_clipboard_selector_checkbox" data-photo-id="{{photo.id}}" onchange="return photo_clipboard.on_photo_select(event);"
 | |
|                 />Clipboard
 | |
|                 </label>
 | |
|             </li>
 | |
|         </ul>
 | |
| 
 | |
|         <!-- CONTAINING ALBUMS -->
 | |
|         {% set albums = photo.get_containing_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 %}
 | |
|         </ul>
 | |
|         {% endif %}
 | |
| 
 | |
|         <!-- BEFORE & AFTER SEARCH LINKS -->
 | |
|         <div id="before_after_links">
 | |
|             <a href="/search?created=-{{photo.created_unix}}">←Before</a>
 | |
|             <span> | </span>
 | |
|             <a href="/search?created={{photo.created_unix}}-&orderby=created-asc">After→</a>
 | |
|         </div>
 | |
| 
 | |
|     </div>
 | |
|     <div id="message_area"></div>
 | |
| </div>
 | |
| 
 | |
| <div id="right">
 | |
|     <!-- THE PHOTO ITSELF -->
 | |
|     <div id="photo_viewer" class="photo_viewer_{{photo.simple_mimetype}}" {%if photo.simple_mimetype == "image"%}onclick="return 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>
 | |
| 
 | |
|         {% else %}
 | |
|         <a href="{{photo|file_link}}">View {{photo.basename}}</a>
 | |
| 
 | |
|         {% endif %}
 | |
|     </div>
 | |
|     <div id="hovering_tools">
 | |
|         {% if photo.simple_mimetype == "video" %}
 | |
|         <button id="generate_thumbnail_button" class="green_button button_with_spinner" onclick="return generate_thumbnail_for_video_form();">Capture thumbnail</button>
 | |
|         {% endif %}
 | |
| 
 | |
|         <button
 | |
|         class="green_button button_with_confirm"
 | |
|         data-holder-id="copy_other_photo_tags_holder"
 | |
|         data-is-input="1"
 | |
|         data-prompt="Other photo ID"
 | |
|         data-cancel-class="gray_button"
 | |
|         data-onclick="return copy_other_photo_tags_form(event);"
 | |
|         >
 | |
|             Copy tags from other photo
 | |
|         </button>
 | |
| 
 | |
|         <button
 | |
|         class="red_button button_with_confirm"
 | |
|         data-onclick="return delete_photo_form();"
 | |
|         data-prompt="Delete photo, keep file?"
 | |
|         data-cancel-class="gray_button"
 | |
|         >
 | |
|             Remove
 | |
|         </button>
 | |
| 
 | |
|         <button
 | |
|         class="red_button button_with_confirm"
 | |
|         data-onclick="return delete_photo_from_disk_form();"
 | |
|         data-prompt="Delete file on disk?"
 | |
|         data-cancel-class="gray_button"
 | |
|         >
 | |
|             Delete
 | |
|         </button>
 | |
|     </div>
 | |
| </div>
 | |
| </div>
 | |
| </body>
 | |
| 
 | |
| <script type="text/javascript">
 | |
| const PHOTO_ID = "{{photo.id}}";
 | |
| 
 | |
| const add_tag_box = document.getElementById('add_tag_textbox');
 | |
| const add_tag_button = document.getElementById('add_tag_button');
 | |
| common.bind_box_to_button(add_tag_box, add_tag_button, false);
 | |
| 
 | |
| const message_area = document.getElementById('message_area');
 | |
| 
 | |
| // API /////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| function add_photo_tag_form()
 | |
| {
 | |
|     const tagname = document.getElementById("add_tag_textbox").value;
 | |
|     if (tagname == "")
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     api.photos.add_tag(PHOTO_ID, tagname, add_photo_tag_callback);
 | |
|     add_tag_box.value = "";
 | |
| }
 | |
| 
 | |
| function add_photo_tag_callback(response)
 | |
| {
 | |
|     const abort = add_remove_photo_tag_callback(response);
 | |
|     if (abort)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     const this_tags = document.getElementById("this_tags");
 | |
|     const tag_cards = this_tags.getElementsByClassName("tag_card");
 | |
|     for (const tag_card of tag_cards)
 | |
|     {
 | |
|         if (tag_card.innerText === response.data.tagname)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     const li = document.createElement("li");
 | |
|     const tag_card = document.createElement("a");
 | |
|     tag_card.className = "tag_card"
 | |
|     tag_card.href = "/tag/" + response.data.tagname;
 | |
|     tag_card.innerText = response.data.tagname;
 | |
|     const remove_button = document.createElement("button");
 | |
|     remove_button.className = "remove_tag_button red_button"
 | |
|     remove_button.onclick = () => remove_photo_tag_form(PHOTO_ID, response.data.tagname);
 | |
|     li.appendChild(tag_card);
 | |
|     li.appendChild(remove_button);
 | |
|     this_tags.appendChild(li);
 | |
|     sort_tag_cards();
 | |
| }
 | |
| 
 | |
| function copy_other_photo_tags_form(event)
 | |
| {
 | |
|     const other_photo = event.target.input_source.value;
 | |
|     if (! other_photo.trim())
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     api.photos.copy_tags(PHOTO_ID, other_photo, common.refresh_or_alert);
 | |
| }
 | |
| 
 | |
| function remove_photo_tag_form(photo_id, tagname)
 | |
| {
 | |
|     api.photos.remove_tag(photo_id, tagname, remove_photo_tag_callback);
 | |
|     add_tag_box.focus();
 | |
| }
 | |
| 
 | |
| function remove_photo_tag_callback(response)
 | |
| {
 | |
|     const abort = add_remove_photo_tag_callback(response);
 | |
|     if (abort)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     const tag_cards = document.getElementById("this_tags").getElementsByClassName("tag_card");
 | |
|     for (const tag_card of tag_cards)
 | |
|     {
 | |
|         if (tag_card.innerText === response.data.tagname)
 | |
|         {
 | |
|             const li = tag_card.parentElement;
 | |
|             li.parentElement.removeChild(li);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function add_remove_photo_tag_callback(response)
 | |
| {
 | |
|     if (! response.meta.json_ok)
 | |
|     {
 | |
|         alert(JSON.stringify(response));
 | |
|         return;
 | |
|     }
 | |
|     let message_text;
 | |
|     let message_positivity;
 | |
|     let abort;
 | |
|     if ("error_type" in response.data)
 | |
|     {
 | |
|         message_positivity = "message_negative";
 | |
|         message_text = response.data.error_message;
 | |
|         abort = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         const tagname = response.data.tagname;
 | |
|         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;
 | |
|         }
 | |
|         abort = false;
 | |
|     }
 | |
|     common.create_message_bubble(message_area, message_positivity, message_text, 8000);
 | |
|     return abort;
 | |
| }
 | |
| 
 | |
| function delete_photo_form()
 | |
| {
 | |
|     api.photos.delete(PHOTO_ID, false, api.photos.callback_go_to_search);
 | |
| }
 | |
| 
 | |
| function delete_photo_from_disk_form()
 | |
| {
 | |
|     api.photos.delete(PHOTO_ID, true, api.photos.callback_go_to_search);
 | |
| }
 | |
| 
 | |
| function generate_thumbnail_for_video_form()
 | |
| {
 | |
|     const timestamp = document.querySelector("#right video").currentTime;
 | |
|     const special = {"timestamp": timestamp};
 | |
|     api.photos.generate_thumbnail(PHOTO_ID, special, generate_thumbnail_callback)
 | |
| }
 | |
| 
 | |
| function generate_thumbnail_callback(response)
 | |
| {
 | |
|     const generate_thumbnail_button = document.getElementById("generate_thumbnail_button");
 | |
|     window[generate_thumbnail_button.dataset.spinnerCloser]();
 | |
|     if (! response.meta.json_ok)
 | |
|     {
 | |
|         alert(JSON.stringify(response));
 | |
|         return;
 | |
|     }
 | |
|     if (response.meta.status == 200)
 | |
|     {
 | |
|         common.create_message_bubble(message_area, "message_positive", "Thumbnail captured", 8000);
 | |
|     }
 | |
|     else if ("error_type" in response.data)
 | |
|     {
 | |
|         common.create_message_bubble(message_area, "message_negative", response.data.error_message, 8000);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         alert(JSON.stringify(response));
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function refresh_metadata_form()
 | |
| {
 | |
|     api.photos.refresh_metadata(PHOTO_ID, common.refresh_or_alert);
 | |
| }
 | |
| 
 | |
| function set_searchhidden_form()
 | |
| {
 | |
|     const checkbox = document.getElementById("searchhidden_checkbox");
 | |
|     if (checkbox.checked)
 | |
|     {
 | |
|         api.photos.set_searchhidden(PHOTO_ID, set_searchhidden_callback);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         api.photos.unset_searchhidden(PHOTO_ID, set_searchhidden_callback);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function set_searchhidden_callback(response)
 | |
| {
 | |
|     if (response.meta.status !== 200)
 | |
|     {
 | |
|         alert(JSON.stringify(response));
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function show_in_folder_form()
 | |
| {
 | |
|     api.photos.show_in_folder(PHOTO_ID, show_in_folder_callback);
 | |
| }
 | |
| 
 | |
| function show_in_folder_callback(response)
 | |
| {
 | |
|     if (response.meta.status !== 200)
 | |
|     {
 | |
|         alert(JSON.stringify(response));
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // UI //////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| function sort_tag_cards()
 | |
| {
 | |
|     const tag_list = document.getElementById("this_tags");
 | |
|     const lis = Array.from(tag_list.children).filter(el => el.getElementsByClassName("tag_card").length);
 | |
|     function compare(li1, li2)
 | |
|     {
 | |
|         const tag1 = li1.querySelector(".tag_card:last-of-type").innerText;
 | |
|         const tag2 = li2.querySelector(".tag_card:last-of-type").innerText;
 | |
|         return tag1 < tag2 ? -1 : 1;
 | |
|     }
 | |
|     lis.sort(compare);
 | |
|     for (const li of lis)
 | |
|     {
 | |
|         tag_list.appendChild(li);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // UI - HOVERZOOM //////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| const ZOOM_BG_URL = "url('{{photo|file_link}}')";
 | |
| function enable_hoverzoom(event)
 | |
| {
 | |
|     //console.log("enable zoom");
 | |
|     const photo_viewer = document.getElementById("photo_viewer");
 | |
|     const photo_img = photo_viewer.children[0];
 | |
|     if (
 | |
|         photo_img.naturalWidth < photo_viewer.offsetWidth &&
 | |
|         photo_img.naturalHeight < photo_viewer.offsetHeight
 | |
|     )
 | |
|     {
 | |
|         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)
 | |
|     return true;
 | |
| }
 | |
| function disable_hoverzoom()
 | |
| {
 | |
|     //console.log("disable zoom");
 | |
|     const photo_viewer = document.getElementById("photo_viewer");
 | |
|     const 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;
 | |
| }
 | |
| function toggle_hoverzoom(event)
 | |
| {
 | |
|     const photo_img = document.getElementById("photo_viewer").children[0];
 | |
|     if (photo_img.style.opacity === "0")
 | |
|     {
 | |
|         disable_hoverzoom();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         enable_hoverzoom(event);
 | |
|     }
 | |
|     if (common.is_wide_mode())
 | |
|     {
 | |
|         add_tag_box.focus();
 | |
|     }
 | |
| }
 | |
| 
 | |
| function move_hoverzoom(event)
 | |
| {
 | |
|     const photo_viewer = document.getElementById("photo_viewer");
 | |
|     const 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);
 | |
|     mouse_x *= 1.05;
 | |
|     mouse_x += (photo_viewer.offsetWidth / 2);
 | |
| 
 | |
|     mouse_y -= (photo_viewer.offsetHeight / 2);
 | |
|     mouse_y *= 1.05;
 | |
|     mouse_y += (photo_viewer.offsetHeight / 2);
 | |
| 
 | |
|     if (photo_img.naturalWidth < photo_viewer.offsetWidth)
 | |
|     {
 | |
|         // If the image is smaller than the frame, just center it
 | |
|         x = (photo_img.naturalWidth - photo_viewer.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_viewer.offsetWidth) * (mouse_x / photo_viewer.offsetWidth);
 | |
|     }
 | |
| 
 | |
|     if (photo_img.naturalHeight < photo_viewer.offsetHeight)
 | |
|     {
 | |
|         y = (photo_img.naturalHeight - photo_viewer.offsetHeight) / 2;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         y = (photo_img.naturalHeight - photo_viewer.offsetHeight) * (mouse_y / photo_viewer.offsetHeight);
 | |
|     }
 | |
|     //console.log(x);
 | |
|     photo_viewer.style.backgroundPosition=(-x)+"px "+(-y)+"px";
 | |
| }
 | |
| 
 | |
| function autofocus_add_tag_box()
 | |
| {
 | |
|     /*
 | |
|     If the add_tag_box has autofocus set by the HTML, then when the screen is
 | |
|     in narrow mode, the autofocusing of the tag box snaps the screen down to it,
 | |
|     which is annoying. So, this function focuses the box manually as long as
 | |
|     we're not narrow.
 | |
|     */
 | |
|     if (common.is_wide_mode())
 | |
|     {
 | |
|         add_tag_box.focus();
 | |
|     }
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| function on_pageload()
 | |
| {
 | |
|     autofocus_add_tag_box();
 | |
|     photo_clipboard.apply_check(document.getElementById("clipboard_checkbox"));
 | |
|     photo_clipboard.register_hotkeys();
 | |
| }
 | |
| document.addEventListener("DOMContentLoaded", on_pageload);
 | |
| </script>
 | |
| </html>
 |