etiquette/frontends/etiquette_flask/templates/clipboard.html

411 lines
12 KiB
HTML

<!DOCTYPE html>
<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="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">
<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/cards.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>
#left
{
display: grid;
grid-row-gap: 8px;
grid-auto-rows: max-content;
}
#right
{
display: grid;
grid-row-gap: 8px;
padding: 8px;
background-color: var(--color_transparency);
}
#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 (min-width: 800px)
{
#right
{
grid-template:
"toolbox" auto
"message_area" 1fr
/ 1fr;
}
}
@media screen and (max-width: 800px)
{
#right
{
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 sticky_bottom_right">
<div id="left">
<div class="panel">The clipboard contains <span class="dynamic_clipboard_count">0</span> items.
<button id="clear_clipboard_button" class="red_button" onclick="return photo_clipboard.clear_clipboard();">Clear it.</button>
</div>
<div id="clipboard_photos_holder" class="photos_holder panel">
</div>
</div>
<div id="right">
<div id="toolbox">
<div id="add_tag_area">
<input type="text" id="add_tag_textbox" class="entry_with_history entry_with_tagname_replacements" list="tag_autocomplete_datalist">
<button class="add_tag_button green_button" id="add_tag_button" onclick="return add_tag_form();">Add tag</button>
</div>
<div id="remove_tag_area">
<input type="text" id="remove_tag_textbox" class="entry_with_history entry_with_tagname_replacements" list="tag_autocomplete_datalist">
<button class="red_button" id="remove_tag_button" onclick="return 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="return refresh_metadata_form();">Refresh metadata</button>
</div>
<div id="searchhidden_area">
<button class="yellow_button" id="set_searchhidden_button" onclick="return set_searchhidden_form();">Searchhide</button>
<button class="yellow_button" id="unset_searchhidden_button" onclick="return unset_searchhidden_form();">Unhide</button>
</div>
<div id="download_zip_area">
<button class="yellow_button" id="download_zip_button" onclick="return download_zip_form();">Download .zip</button>
</div>
</div>
<div id="message_area">
</div>
</div>
</div>
</body>
<script type="text/javascript">
// divs maps photo IDs to the photo card div which will be shown in the holder.
// They are stored in this map so we can update them from API data without
// navigating the dom for them.
const divs = {};
const needed = new Set();
const holder = document.getElementById("clipboard_photos_holder");
const add_box = document.getElementById("add_tag_textbox");
const add_button = document.getElementById("add_tag_button");
common.bind_box_to_button(add_box, add_button);
const remove_box = document.getElementById("remove_tag_textbox");
const remove_button = document.getElementById("remove_tag_button");
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.clear();
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.
*/
// 'in' instead of 'of' is intentional here because divs is a dict.
for (const photo_id in divs)
{
const photo_div = divs[photo_id];
const should_keep = photo_clipboard.clipboard.has(photo_id);
const on_page = holder.contains(photo_div);
if (on_page && !should_keep)
{
holder.removeChild(photo_div)
}
if (!on_page && should_keep)
{
holder.appendChild(photo_div)
}
}
photo_clipboard.apply_check_all();
if (holder.childElementCount == 0)
{
holder.classList.add("hidden");
}
else
{
holder.classList.remove("hidden");
}
}
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;
}
const url = "/batch/photos";
const data = new FormData();
const photo_ids = Array.from(needed).join(",");
data.append("photo_ids", photo_ids);
function callback(response)
{
if (response.meta.status !== 200)
{
alert(JSON.stringify(response));
return;
}
for (const photo of response.data)
{
photo_div = cards.photos.create(photo);
divs[photo.id] = photo_div;
needed.delete(photo.id)
}
photo_clipboard.apply_check_all();
if (needed.size > 0)
{
console.log("Needed but not received: " + Array.from(needed));
}
refresh_divs();
}
common.post(url, data, callback);
}
function my_clipboard_load_save_hook()
{
recalculate_needed();
request_more_divs();
refresh_divs();
}
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)
{
if (! response.meta.json_ok)
{
alert(JSON.stringify(response));
return;
}
const tagname = response.data.tagname;
const 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)
{
const 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;}
const box = document.getElementById("add_tag_textbox");
const tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
const 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;}
const box = document.getElementById("remove_tag_textbox");
const tagname = box.value.trim();
if (! tagname)
{return}
box.value = "";
const 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;}
const photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.get_download_zip_token(photo_ids, api.photos.callback_download_zip);
}
////////////////////////////////////////////////////////////////////////////////
const refresh_metadata_button = document.getElementById("refresh_metadata_button");
function refresh_metadata_callback(response)
{
window[refresh_metadata_button.dataset.spinnerCloser]();
if (! response.meta.json_ok)
{
alert(JSON.stringify(response));
return;
}
if ("error_type" in response.data)
{
const message_area = document.getElementById("message_area");
const message_positivity = "message_negative";
const 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)
{
return spinners.BAIL;
}
const photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_refresh_metadata(photo_ids, refresh_metadata_callback);
}
////////////////////////////////////////////////////////////////////////////////
function set_unset_searchhidden_callback(response)
{
if (! response.meta.json_ok)
{
alert(JSON.stringify(response));
return;
}
const 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;}
const 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;}
const photo_ids = Array.from(photo_clipboard.clipboard);
api.photos.batch_unset_searchhidden(photo_ids, set_unset_searchhidden_callback);
}
function on_pageload()
{
photo_clipboard.register_hotkeys();
}
document.addEventListener("DOMContentLoaded", on_pageload);
</script>
</html>