etiquette/frontends/etiquette_flask/templates/clipboard.html

379 lines
10 KiB
HTML

<!DOCTYPE html5>
<html>
<head>
{% import "header.html" as header %}
{% import "clipboard_tray.html" as clipboard_tray %}
<title>Clipboard</title>
<meta charset="UTF-8">
<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">
<link rel="stylesheet" href="/static/css/photo_card.css">
<link rel="stylesheet" href="/static/css/clipboard_tray.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/spinner.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
<style>
#left
{
word-break: break-word;
}
#right
{
grid-row-gap: 8px;
grid-template:
"toolbox" auto
"message_area" 1fr
/ 1fr;
}
#toolbox
{
display: grid;
grid-auto-rows: min-content;
grid-row-gap: 8px;
}
#toolbox > *
{
display: flex;
}
#toolbox input
{
margin: 0;
}
#toolbox .spinner_holder
{
display: flex;
flex: 1;
}
#toolbox button
{
flex: 1;
}
#toolbox { grid-area: toolbox; }
#message_area { grid-area: message_area; }
#add_tag_area input,
#remove_tag_area input
{
flex: 1;
}
#add_tag_area button,
#remove_tag_area button
{
flex: initial;
}
@media screen and (max-width: 800px)
{
#content_body
{
grid-template:
"left" 1fr
"right" 200px
/ 1fr !important;
}
#right
{
top: unset !important;
width: unset !important;
left: 8px;
right: 8px;
bottom: 8px;
height: 200px;
grid-template:
"toolbox message_area" 1fr
/1fr minmax(50px, 200px);
}
}
</style>
</head>
<body>
{{header.make_header(session=session)}}
<div id="content_body" class="sticky_side_right">
<div id="left">
<div class="panel">The clipboard contains <span class="clipboard_count">0</span> items.
<button id="clear_clipboard_button" class="red_button" onclick="photo_clipboard.clear_clipboard()">Clear it.</button>
</div>
<div id="photo_card_holder">
</div>
</div>
<div id="right">
<div id="toolbox">
<div id="add_tag_area">
<input type="text" id="add_tag_textbox" list="tag_autocomplete_datalist">
<button class="add_tag_button green_button" id="add_tag_button" onclick="add_tag_form();">Add tag</button>
</div>
<div id="remove_tag_area">
<input type="text" id="remove_tag_textbox" list="tag_autocomplete_datalist">
<button class="red_button" id="remove_tag_button" onclick="remove_tag_form();">Remove tag</button>
</div>
<div id="refresh_metadata_area">
<button class="green_button button_with_spinner" id="refresh_metadata_button" data-spinner-delay="500" onclick="refresh_metadata_form();">Refresh metadata</button>
</div>
<div id="searchhidden_area">
<button class="yellow_button" id="set_searchhidden_button" onclick="set_searchhidden_form()">Searchhide</button>
<button class="yellow_button" id="unset_searchhidden_button" onclick="unset_searchhidden_form()">Unhide</button>
</div>
<div id="download_zip_area">
<button class="yellow_button" id="download_zip_button" onclick="download_zip_form()">Download .zip</button>
</div>
</div>
<div id="message_area">
</div>
</div>
</div>
</body>
<script type="text/javascript">
var divs = {};
var needed = new Set();
var holder = document.getElementById("photo_card_holder");
var add_box = document.getElementById("add_tag_textbox");
var add_button = document.getElementById("add_tag_button");
add_box.addEventListener("keyup", common.entry_with_history_hook);
common.bind_box_to_button(add_box, add_button);
var remove_box = document.getElementById("remove_tag_textbox");
var remove_button = document.getElementById("remove_tag_button");
remove_box.addEventListener("keyup", common.entry_with_history_hook);
common.bind_box_to_button(remove_box, remove_button);
function recalculate_needed()
{
/*
Populate the global `needed` set with all photo ids which are on the
clipboard but not on the page yet. When this page is first loaded, that
will be all ids. If the user adds more photos to their clipboard in a
different tab and returns to this tab, then the new ids will be needed.
This function only calculates which ids are needed. The actual fetching of
divs is in `request_more_divs`.
*/
needed = new Set();
photo_clipboard.clipboard.forEach(function(photo_id)
{
if (!(photo_id in divs))
{
needed.add(photo_id);
}
});
}
function refresh_divs()
{
/*
Add new divs to the page, and remove divs which the user has removed from
their clipboard.
*/
for (var photo_id in divs)
{
var photo_div = divs[photo_id];
var should_keep = photo_clipboard.clipboard.has(photo_id);
var on_page = holder.contains(photo_div);
if (on_page && !should_keep)
{
holder.removeChild(photo_div)
}
if (!on_page && should_keep)
{
holder.appendChild(photo_div)
}
}
}
function request_more_divs()
{
/*
Using the ids in `needed`, download more photo card divs and place them
into `divs`, so that `refresh_divs` can then add them to the page.
*/
if (needed.size == 0)
{
return;
}
var url = "/batch/photos/photo_card";
var data = new FormData();
var photo_ids = Array.from(needed).join(",");
data.append("photo_ids", photo_ids);
function callback(response)
{
if (response.meta.status !== 200)
{
return;
}
for (photo_id in response.data)
{
photo_div = common.html_to_element(response.data[photo_id]);
divs[photo_id] = photo_div;
needed.delete(photo_id)
holder.appendChild(photo_div);
}
photo_clipboard.apply_check_all();
console.log("Needed but not received: " + Array.from(needed));
}
common.post(url, data, callback);
}
function my_clipboard_load_save_hook()
{
recalculate_needed();
request_more_divs();
refresh_divs();
}
tag_autocomplete.init_datalist();
photo_clipboard.on_load_hooks.push(my_clipboard_load_save_hook);
photo_clipboard.on_save_hooks.push(my_clipboard_load_save_hook);
////////////////////////////////////////////////////////////////////////////////////////////////////
function add_remove_tag_callback(response)
{
var tagname = response.data.tagname;
var message_area = document.getElementById("message_area");
var message_positivity;
var message_text;
if ("error_type" in response.data)
{
message_positivity = "message_negative";
message_text = response.data.error_message;
}
else if ("action" in response.data)
{
var action = response.data.action;
message_positivity = "message_positive";
if (action == "add")
{message_text = "Added tag " + tagname;}
else if (action == "remove")
{message_text = "Removed tag " + tagname;}
}
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
}
function add_tag_form()
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var box = document.getElementById("add_tag_textbox");
var tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_add_tag(photo_ids, tagname, add_remove_tag_callback);
}
function remove_tag_form()
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var box = document.getElementById("remove_tag_textbox");
var tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_remove_tag(photo_ids, tagname, add_remove_tag_callback);
}
////////////////////////////////////////////////////////////////////////////////
function download_zip_form()
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.get_download_zip_token(photo_ids, api.photos.callback_download_zip);
}
////////////////////////////////////////////////////////////////////////////////
var refresh_metadata_button = document.getElementById("refresh_metadata_button");
function refresh_metadata_callback(response)
{
window[refresh_metadata_button.dataset.spinnerCloser]();
if ("error_type" in response.data)
{
var message_area = document.getElementById("message_area");
var message_positivity = "message_negative";
var message_text = response.data.error_message;
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
}
else
{
common.refresh();
}
}
function refresh_metadata_form()
{
if (photo_clipboard.clipboard.size == 0)
{
window[refresh_metadata_button.dataset.spinnerCloser]();
return;
}
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_refresh_metadata(photo_ids, refresh_metadata_callback);
}
////////////////////////////////////////////////////////////////////////////////
function set_unset_searchhidden_callback(response)
{
var message_area = document.getElementById("message_area");
var message_positivity;
var message_text;
if ("error_type" in response.data)
{
message_positivity = "message_negative";
message_text = response.data.error_message;
}
else
{
message_positivity = "message_positive";
message_text = "Success."
}
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
}
function set_searchhidden_form()
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_set_searchhidden(photo_ids, set_unset_searchhidden_callback);
}
function unset_searchhidden_form()
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_unset_searchhidden(photo_ids, set_unset_searchhidden_callback);
}
</script>
</html>