Add new page /clipboard, with full photo cards.

master
voussoir 2018-02-17 19:12:34 -08:00
parent ef5bbf5fc3
commit 91d445a877
7 changed files with 154 additions and 1 deletions

View File

@ -79,7 +79,6 @@ If you are interested in helping, please raise an issue before making any pull r
- Currently, the Jinja templates are having a tangling influence on the backend objects, because Jinja cannot import my other modules like bytestring, but it can access the methods of the objects I pass into the template. As a result, the objects have excess helper methods. Consider making them into Jinja filters instead. Which is also kind of ugly but will move that pollution out of the backend at least.
- Perhaps instead of actually deleting objects, they should just have a `deleted` flag, to make easy restoration possible. Also consider regrouping the children of restored Groupables if those children haven't already been reassigned somewhere else.
- Add a new table to store permanent history of add/remove of tags on photos, so that accidents or trolling can be reversed.
- Currently, the photo clipboard only stores IDs and therefore when we construct the clipboard tray elements we cannot provide more rich information like filename, the user is only presented with a list of IDs which they probably don't care about. Should the localstorage cache some other more user-friendly information?
- Improve transaction rollbacking. I'm not satisfied with the @transaction decorator because sometimes I want to use exceptions as control flow without them rolling things back. Context managers are good but it's a matter of how abstracted they should be.
### To do list: User permissions

View File

@ -115,6 +115,40 @@ def post_photo_refresh_metadata(photo_id):
return jsonify.make_json_response({})
# Clipboard ########################################################################################
@site.route('/clipboard')
@session_manager.give_token
def get_clipboard_page():
return flask.render_template('clipboard.html')
@site.route('/photo_cards', methods=['POST'])
def get_photo_cards():
photo_ids = request.form.get('photo_ids', None)
if photo_ids is None:
return jsonify.make_json_response({})
photo_ids = etiquette.helpers.comma_space_split(photo_ids)
photos = [common.P_photo(photo_id, response_type='html') for photo_id in photo_ids]
# Photo filenames are prevented from having colons, so using it as a split
# delimiter should be safe.
template = '''
{% import "photo_card.html" as photo_card %}
{% for photo in photos %}
{{photo.id}}:
{{photo_card.create_photo_card(photo)}}
:SPLITME:
{% endfor %}
'''
html = flask.render_template_string(template, photos=photos)
divs = [div.strip() for div in html.split(':SPLITME:')]
divs = [div for div in divs if div]
divs = [div.split(':', 1) for div in divs]
divs = {photo_id.strip(): photo_card.strip() for (photo_id, photo_card) in divs}
response = jsonify.make_json_response(divs)
return response
# Search ###########################################################################################
def get_search_core():

View File

@ -305,3 +305,8 @@ is hovered over.
width: 300px;
overflow-y: auto;
}
#clipboard_tray_toolbox
{
display: flex;
flex-direction: column;
}

View File

@ -356,3 +356,10 @@ function entry_with_history_hook(box, button)
box.entry_history_pos = -1;
}
}
function html_to_element(html)
{
var template = document.createElement("template");
template.innerHTML = html;
return template.content.firstChild;
}

View File

@ -173,6 +173,12 @@ function update_clipboard_tray()
Update the clipboard's title bar to the correct number of items and rebuild
the rows if the tray is open.
*/
var clipboard_tray = document.getElementById("clipboard_tray");
if (clipboard_tray === null)
{
return;
}
var tray_button = document.getElementById("clipboard_tray_expandbutton");
if (tray_button !== null)
{

View File

@ -0,0 +1,101 @@
<!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/common.css">
<script src="/static/common.js"></script>
<script src="/static/photoclipboard.js"></script>
<style>
</style>
</head>
<body>
{{header.make_header(session=session)}}
<div id="content_body">
<div id="photo_card_holder">
</div>
</div>
</body>
<script type="text/javascript">
var divs = {};
var needed = new Set();
var holder = document.getElementById("photo_card_holder");
function recalculate_needed()
{
needed = new Set();
photo_clipboard.forEach(function(photo_id)
{
if (!(photo_id in divs))
{
needed.add(photo_id);
}
});
}
function refresh_divs()
{
for (var photo_id in divs)
{
var photo_div = divs[photo_id];
var should_keep = photo_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()
{
if (needed.size == 0)
{
return;
}
var url = "/photo_cards";
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;
}
response = response["data"];
var holder = document.getElementById("photo_card_holder");
for (photo_id in response)
{
photo_div = html_to_element(response[photo_id]);
divs[photo_id] = photo_div;
needed.delete(photo_id)
holder.appendChild(photo_div);
}
apply_check_all();
}
post(url, data, callback);
}
function myhook()
{
recalculate_needed();
request_more_divs();
refresh_divs();
}
on_clipboard_load_hooks.push(myhook);
</script>
</html>

View File

@ -7,6 +7,7 @@
>Clipboard: 0 items</button>
<div id="clipboard_tray_body" class="hidden">
<div id="clipboard_tray_toolbox">
<a target="_blank" href="/clipboard">Full clipboard</a>
</div>
<div id="clipboard_tray_lines">
</div>