Ethan Dalool
f56da72881
It's better semantically for these items coming out of the backend to be sets. Sorted lists are only relevant to human consumption at the frontend.
445 lines
13 KiB
HTML
445 lines
13 KiB
HTML
<!DOCTYPE html5>
|
|
<html>
|
|
|
|
{% macro shared_css() %}
|
|
<style>
|
|
h2, h3
|
|
{
|
|
margin-top: 0;
|
|
}
|
|
#album_metadata h2 .editor_input
|
|
{
|
|
font-size: inherit;
|
|
font-weight: inherit;
|
|
}
|
|
#description_text
|
|
{
|
|
font-family: initial;
|
|
padding: 8px;
|
|
}
|
|
|
|
#left
|
|
{
|
|
display: grid;
|
|
grid-row-gap: 30px;
|
|
grid-auto-rows: max-content;
|
|
}
|
|
#right
|
|
{
|
|
display: grid;
|
|
grid-row-gap: 8px;
|
|
grid-auto-rows: min-content;
|
|
padding: 8px;
|
|
background-color: var(--color_transparency);
|
|
}
|
|
|
|
#right > *
|
|
{
|
|
/* Fixes button_with_confirm, spinner, etc. from width-jumping on load,
|
|
because originally the button is occupying the full width and then becomes
|
|
small when it gets put into the holder */
|
|
margin-right: auto;
|
|
|
|
/* Fixes the button_with_confirm inputs from causing the overall row to
|
|
become wider than the containing parent when expanded. */
|
|
word-break: break-all;
|
|
}
|
|
#right input
|
|
{
|
|
margin: 0;
|
|
}
|
|
#right .confirm_holder_stage2
|
|
{
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
#right .confirm_holder_stage2 span,
|
|
#right .confirm_holder_stage2 input
|
|
{
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.remove_child_button
|
|
{
|
|
display: none;
|
|
}
|
|
.remove_child_button:hover,
|
|
.album_card:hover .remove_child_button
|
|
{
|
|
display: initial;
|
|
}
|
|
</style>
|
|
{% endmacro %}
|
|
|
|
{% if album is not defined %} {## Album listing ###################################################}
|
|
|
|
<head>
|
|
{% import "header.html" as header %}
|
|
{% import "album_card.html" as album_card %}
|
|
<title>Albums</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">
|
|
{% 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>
|
|
|
|
{{shared_css()}}
|
|
</head>
|
|
|
|
<body>
|
|
{{header.make_header(session=session)}}
|
|
<div id="content_body" class="sticky_side_right sticky_bottom_right">
|
|
<div id="left">
|
|
<div id="album_list" class="panel">
|
|
<h2>Albums</h2>
|
|
{% for album in albums %}
|
|
{{album_card.create_album_card(album, view=view, draggable=true)}}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div id="right">
|
|
{% if view != "list" %}
|
|
<a href="?view=list">List view</a>
|
|
{% else %}
|
|
<a href="?view=grid">Grid view</a>
|
|
{% endif %}
|
|
<button
|
|
class="green_button button_with_confirm"
|
|
data-is-input="1"
|
|
data-prompt="Album title"
|
|
data-cancel-class="gray_button"
|
|
data-onclick="return create_child(event.target.input_source.value);"
|
|
>
|
|
Create album
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
<script id="album_listing_script" type="text/javascript">
|
|
const ALBUM_ID = undefined;
|
|
</script>
|
|
|
|
{% else %} {## Individual album ###################################################################}
|
|
|
|
<head>
|
|
{% import "header.html" as header %}
|
|
{% import "album_card.html" as album_card %}
|
|
{% import "photo_card.html" as photo_card %}
|
|
{% import "clipboard_tray.html" as clipboard_tray %}
|
|
<title>{{album.display_name}} | Albums</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/clipboard_tray.css">
|
|
<link rel="stylesheet" href="/static/css/photo_card.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/spinner.js"></script>
|
|
<script src="/static/js/editor.js"></script>
|
|
<script src="/static/js/hotkeys.js"></script>
|
|
<script src="/static/js/photo_clipboard.js"></script>
|
|
|
|
{{shared_css()}}
|
|
</head>
|
|
|
|
<body>
|
|
{{header.make_header(session=session)}}
|
|
<div id="content_body" class="sticky_side_right sticky_bottom_right">
|
|
<div id="right">
|
|
{% if view != "list" %}
|
|
<a href="?view=list">List view</a>
|
|
{% else %}
|
|
<a href="?view=grid">Grid view</a>
|
|
{% endif %}
|
|
|
|
<button
|
|
class="red_button button_with_confirm"
|
|
data-onclick="return delete_album_form();"
|
|
data-prompt="Delete Album?"
|
|
data-cancel-class="gray_button"
|
|
>
|
|
Delete
|
|
</button>
|
|
|
|
<button
|
|
class="green_button button_with_confirm"
|
|
data-is-input="1"
|
|
data-prompt="Album title"
|
|
data-cancel-class="gray_button"
|
|
data-onclick="return create_child(event.target.input_source.value);"
|
|
>
|
|
Create child
|
|
</button>
|
|
|
|
<button
|
|
class="green_button button_with_confirm"
|
|
data-is-input="1"
|
|
data-prompt="Child ID"
|
|
data-cancel-class="gray_button"
|
|
data-onclick="return add_child(event.target.input_source.value);"
|
|
>
|
|
Add child
|
|
</button>
|
|
|
|
{% set associated_directories = album.get_associated_directories() %}
|
|
<button
|
|
class="green_button button_with_spinner"
|
|
onclick="return refresh_associated_directories_form();"
|
|
data-spinner-delay="500"
|
|
{% if associated_directories %}
|
|
title="Pull from {{associated_directories|length}} directories"
|
|
{% else %}
|
|
title="No associated directories"
|
|
disabled
|
|
{% endif %}
|
|
>
|
|
Refresh directories
|
|
</button>
|
|
</div>
|
|
|
|
<div id="left">
|
|
<div id="hierarchy_self" class="panel">
|
|
<div id="album_metadata">
|
|
<h2><span
|
|
id="title_text"
|
|
data-editor-id="title"
|
|
data-editor-empty-text="{{album.id}}"
|
|
data-editor-placeholder="title"
|
|
>
|
|
{{-album.display_name-}}
|
|
</span></h2>
|
|
|
|
<pre
|
|
id="description_text"
|
|
data-editor-id="description"
|
|
data-editor-placeholder="description"
|
|
{% if not album.description %}class="hidden"{% endif %}
|
|
>
|
|
{{-album.description-}}
|
|
</pre>
|
|
</div>
|
|
<button class="green_button editor_toolbox_placeholder">Edit</button>
|
|
</div>
|
|
|
|
<div id="hierarchy_parents" class="panel">
|
|
{% set parents = album.get_parents() %}
|
|
{% if parents %}
|
|
<h3>{{parents|length}} Parents</h3>
|
|
{% for parent in parents %}
|
|
{{album_card.create_album_card(parent, view=view)}}
|
|
{% endfor %}
|
|
{% else %}
|
|
<h3>1 Parent</h3>
|
|
{{album_card.create_album_card("root", view=view)}}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% set sub_albums = album.get_children() %}
|
|
{% if sub_albums %}
|
|
<div id="hierarchy_children" class="panel">
|
|
<h3>{{sub_albums|length}} Children</h3>
|
|
{% for sub_album in sub_albums|sort(attribute='title') %}
|
|
{{album_card.create_album_card(sub_album, view=view, unlink_parent=album, draggable=true)}}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set photos = album.get_photos() %}
|
|
{% if photos %}
|
|
<div id="hierarchy_photos" class="panel">
|
|
<h3>{{photos|length}} Photos</h3>
|
|
<div id="photo_list">
|
|
{% for photo in photos|sort(attribute='basename', case_sensitive=False) %}
|
|
{{photo_card.create_photo_card(photo, view=view)}}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set has_local_photos = photos|length > 0 %}
|
|
{% set has_child_photos = album.has_any_subalbum_photo() %}
|
|
{% if has_local_photos or has_child_photos %}
|
|
<div id="download_links" class="panel">
|
|
<h3>Download</h3>
|
|
{% if has_local_photos %}
|
|
<p><a id="download_link_single" href="/album/{{album.id}}.zip?recursive=no">These files – {{album.sum_bytes(recurse=False)|bytestring}}</a></p>
|
|
{% endif %}
|
|
{% if has_child_photos %}
|
|
<p><a id="download_link_recursive" href="/album/{{album.id}}.zip?recursive=yes">Include children – {{album.sum_bytes(recurse=True)|bytestring}}</a></p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{{clipboard_tray.clipboard_tray()}}
|
|
<div class="my_clipboard_tray_toolbox">
|
|
<button class="green_button" onclick="return paste_photo_clipboard();">Add to this album</button>
|
|
<button class="red_button" onclick="return unpaste_photo_clipboard();">Remove from this album</button>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
<script id="album_individual_script" type="text/javascript">
|
|
const ALBUM_ID = "{{album.id}}";
|
|
|
|
function add_child(child_id)
|
|
{
|
|
if (! child_id.trim())
|
|
{
|
|
return;
|
|
}
|
|
api.albums.add_child(ALBUM_ID, child_id, common.refresh);
|
|
}
|
|
|
|
function delete_album_form()
|
|
{
|
|
api.albums.delete(ALBUM_ID, api.albums.callback_go_to_albums);
|
|
}
|
|
|
|
function refresh_associated_directories_form()
|
|
{
|
|
api.albums.refresh_directories(ALBUM_ID, common.refresh);
|
|
}
|
|
|
|
function paste_photo_clipboard()
|
|
{
|
|
const photo_ids = Array.from(photo_clipboard.clipboard);
|
|
api.albums.add_photos(ALBUM_ID, photo_ids, common.refresh);
|
|
}
|
|
function unpaste_photo_clipboard()
|
|
{
|
|
const photo_ids = Array.from(photo_clipboard.clipboard);
|
|
api.albums.remove_photos(ALBUM_ID, photo_ids, common.refresh);
|
|
}
|
|
|
|
function on_open(ed, edit_element_map, display_element_map)
|
|
{
|
|
ed.open();
|
|
edit_element_map["title"].focus();
|
|
}
|
|
|
|
function on_save(ed, edit_element_map, display_element_map)
|
|
{
|
|
function callback(response)
|
|
{
|
|
if (response.meta.status != 200)
|
|
{
|
|
ed.show_error("Status: " + response.meta.status);
|
|
return;
|
|
}
|
|
|
|
ed.save();
|
|
|
|
const title_display = display_element_map["title"];
|
|
const description_display = display_element_map["description"];
|
|
|
|
document.title = title_display.innerText + " | Albums";
|
|
|
|
if (description_display.innerText == "")
|
|
{
|
|
description_display.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
edit_element_map["title"].value = edit_element_map["title"].value.trim();
|
|
const title = edit_element_map["title"].value;
|
|
const description = edit_element_map["description"].value;
|
|
|
|
ed.show_spinner();
|
|
api.albums.edit(ALBUM_ID, title, description, callback);
|
|
}
|
|
|
|
function on_cancel(ed, edit_element_map, display_element_map)
|
|
{
|
|
ed.cancel();
|
|
if (display_element_map["description"].innerText == "")
|
|
{
|
|
display_element_map["description"].classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
const title_text = document.getElementById("title_text");
|
|
const description_text = document.getElementById("description_text");
|
|
const ed = new editor.Editor([title_text, description_text], on_open, on_save, on_cancel);
|
|
</script>
|
|
|
|
{% endif %} {## Shared ############################################################################}
|
|
|
|
<script id="album_shared_script" type="text/javascript">
|
|
|
|
function create_child(title)
|
|
{
|
|
if (! title)
|
|
{
|
|
title = undefined;
|
|
}
|
|
const parent_id = ALBUM_ID;
|
|
api.albums.create(title, parent_id, api.albums.callback_follow);
|
|
}
|
|
|
|
function on_album_drag_start(event)
|
|
{
|
|
const album_card = event.target.closest(".album_card");
|
|
event.dataTransfer.setData("text/plain", album_card.id);
|
|
}
|
|
function on_album_drag_end(event)
|
|
{
|
|
}
|
|
function on_album_drag_over(event)
|
|
{
|
|
event.preventDefault();
|
|
}
|
|
function on_album_drag_drop(event)
|
|
{
|
|
const child = document.getElementById(event.dataTransfer.getData("text"));
|
|
const child_id = child.dataset.id;
|
|
const parent = event.currentTarget;
|
|
const parent_id = parent.dataset.id;
|
|
event.dataTransfer.clearData();
|
|
|
|
if (child_id == parent_id)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let prompt;
|
|
if (parent_id === "root")
|
|
{
|
|
const child_title = child.querySelector('.album_card_title').textContent.trim();
|
|
prompt = `Remove child\n${child_title}?`;
|
|
}
|
|
else
|
|
{
|
|
const child_title = child.querySelector('.album_card_title').textContent.trim();
|
|
const parent_title = parent.querySelector('.album_card_title').textContent.trim();
|
|
prompt = `Move\n${child_title}\ninto\n${parent_title}?`;
|
|
}
|
|
|
|
if (! confirm(prompt))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (parent_id === "root")
|
|
{
|
|
api.albums.remove_child(ALBUM_ID, child_id, common.refresh);
|
|
}
|
|
else if (ALBUM_ID)
|
|
{
|
|
api.albums.add_child(parent_id, child_id, null);
|
|
api.albums.remove_child(ALBUM_ID, child_id, common.refresh);
|
|
}
|
|
else
|
|
{
|
|
api.albums.add_child(parent_id, child_id, common.refresh);
|
|
}
|
|
}
|
|
</script>
|
|
</html>
|