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();
for (const photo_id of photo_clipboard.clipboard)
{
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 (const photo_id in divs)
{
let photo_div = divs[photo_id];
let should_keep = photo_clipboard.clipboard.has(photo_id);
let 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;
}
let url = "/batch/photos/photo_card";
let data = new FormData();
let 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)
{
let tagname = response.data.tagname;
let message_area = document.getElementById("message_area");
let message_positivity;
let message_text;
if ("error_type" in response.data)
{
message_positivity = "message_negative";
message_text = response.data.error_message;
}
else if ("action" in response.data)
{
let 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;}
let box = document.getElementById("add_tag_textbox");
let tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
let 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;}
let box = document.getElementById("remove_tag_textbox");
let tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
let 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;}
let 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)
{
let message_area = document.getElementById("message_area");
let message_positivity = "message_negative";
let 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;
}
let photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_refresh_metadata(photo_ids, refresh_metadata_callback);
}
////////////////////////////////////////////////////////////////////////////////
function set_unset_searchhidden_callback(response)
{
let message_area = document.getElementById("message_area");
let message_positivity;
let 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;}
let 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;}
let photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_unset_searchhidden(photo_ids, set_unset_searchhidden_callback);
}
</script>
</html>