<!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 { grid-row-gap: 8px; } #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; } @media screen and (max-width: 800px) { #content_body { grid-template: "left" 1fr "right" 150px / 1fr !important; } #left { margin-right: unset; } #right { top: unset !important; width: unset !important; left: 8px; right: 8px; bottom: 8px; height: 150px; } } </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"> <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="create_child(event.target.input_source.value);" > Create album </button> </div> </div> </body> <script id="album_listing_script" type="text/javascript"> 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"> <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="api.albums.delete(ALBUM_ID, api.albums.callback_go_to_albums)" 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="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="add_child(event.target.input_source.value);" > Add child </button> <button class="green_button button_with_spinner" onclick="api.albums.refresh_directories(ALBUM_ID, common.refresh);" data-spinner-delay="500" {% if not album.get_associated_directories() %} 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 %} {{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="paste_photo_clipboard()">Add to this album</button> <button class="red_button" onclick="unpaste_photo_clipboard()">Remove from this album</button> </div> </div> </body> <script id="album_individual_script" type="text/javascript"> var 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 paste_photo_clipboard() { let photo_ids = Array.from(photo_clipboard.clipboard); api.albums.add_photos(ALBUM_ID, photo_ids, common.refresh); } function unpaste_photo_clipboard() { let 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(); let title_display = display_element_map["title"]; let 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(); let title = edit_element_map["title"].value; let 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"); } } var title_text = document.getElementById("title_text"); var description_text = document.getElementById("description_text"); var 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; } let parent_id = ALBUM_ID; api.albums.create(title, parent_id, api.albums.callback_follow); } function get_album_card_from_child(element) { /* Given an element which is known to be a child of an album card, navigate up the parent tree until we find that album card. This is used to make drag-and-drop work even when you start your drag by clicking on the album's thumbnail, title, etc. */ while (! element.classList.contains("album_card")) { element = element.parentElement; } return element; } function on_album_drag_start(event) { let album_card = get_album_card_from_child(event.target); 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) { let child_id = event.dataTransfer.getData("text"); let child = document.getElementById(child_id); child_id = child.dataset.id; let parent = event.currentTarget; let parent_id = parent.dataset.id; event.dataTransfer.clearData(); if (child_id == parent_id) { return; } let child_title = child.querySelector('.album_card_title').textContent.trim(); let parent_title = parent.querySelector('.album_card_title').textContent.trim(); if (confirm(`Move\n${child_title}\ninto\n${parent_title}?`)) { 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>