<!DOCTYPE html5> <html> <head> {% import "photo_card.html" as photo_card %} {% import "header.html" as header %} {% import "tag_object.html" as tag_object %} {% import "clipboard_tray.html" as clipboard_tray %} <title>Search</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/photo_card.css"> <link rel="stylesheet" href="/static/css/clipboard_tray.css"> <script src="/static/js/common.js"></script> <script src="/static/js/photoclipboard.js"></script> <style> form { display: flex; flex-direction: column; justify-content: center; width: 100%; } #error_message_area { display: flex; flex-direction: column; justify-content: center; align-items: center; } .search_warning { align-self: center; padding: 2px; background-color: #f00; color: #fff; } #left { flex: 1; padding: 8px; max-width: 300px; min-width: 300px; width: 300px; background-color: rgba(0, 0, 0, 0.1); word-wrap: break-word; } #right { flex: 1; padding: 8px; /* Keep the prev-next buttons from scraping the floor */ padding-bottom: 30px; width: auto; } @media screen and (max-width: 800px) { #content_body { flex-direction: column-reverse; } #left { max-width: none; width: initial; } } .prev_next_holder { display: flex; flex-direction: row; } .prev_page { margin-right: 10px; } .next_page { margin-left: 10px; } .prev_page, .next_page { display: flex; flex: 1; justify-content: center; border: 1px solid black; background-color: #ffffd4; font-size: 20; } .search_builder_tagger, #search_builder_orderby_ul { margin: 0; } </style> {% macro prev_next_buttons() %} {% if prev_page_url or next_page_url %} <div class="prev_next_holder"> {% if prev_page_url %} <a class="prev_page" href="{{prev_page_url}}">Previous</a> {% else %} <a class="prev_page"><br></a> {% endif %} {% if next_page_url %} <a class="next_page" href="{{next_page_url}}">Next</a> {% else %} <a class="next_page"><br></a> {% endif %} </div> {% endif %} {% endmacro %} {% macro create_orderby_li(selected_column, selected_sorter) %} <li class="search_builder_orderby_li"> <select> <option value="created" {%if selected_column=="created"%}selected{%endif%} >Creation date</option> <option value="area" {%if selected_column=="area"%}selected{%endif%} >Area</option> <option value="width" {%if selected_column=="width"%}selected{%endif%} >Width</option> <option value="height" {%if selected_column=="height"%}selected{%endif%} >Height</option> <option value="ratio" {%if selected_column=="ratio"%}selected{%endif%} >Aspect Ratio</option> <option value="bytes" {%if selected_column=="bytes"%}selected{%endif%} >File size</option> <option value="duration" {%if selected_column=="duration"%}selected{%endif%} >Duration</option> <option value="tagged_at" {%if selected_column=="tagged_at"%}selected{%endif%}>Recently tagged</option> <option value="random" {%if selected_column=="random"%}selected{%endif%} >Random</option> </select> <select> <option value="desc" {%if selected_sorter=="desc"%}selected{%endif%} >Descending</option> <option value="asc" {%if selected_sorter=="asc"%}selected{%endif%} >Ascending</option> </select> <button class="remove_tag_button_perm red_button" onclick="orderby_remove_hook(this);"></button> </li> {% endmacro %} </head> <body> {{header.make_header(session=session)}} <div id="error_message_area"> {% for warning in warnings %} <span class="search_warning">{{warning}}</span> {% endfor %} </div> <div id="content_body"> <div id="left"> {% for tagtype in ["musts", "mays", "forbids"] %} <div id="search_builder_{{tagtype}}" {% if search_kwargs["tag_expression"]%}style="display:none"{%endif%}> <span>Tag {{tagtype}}:</span> <ul class="search_builder_tagger"> {% set key="tag_" + tagtype %} {% if search_kwargs[key] %} {% for tagname in search_kwargs[key] %} <li class="search_builder_{{tagtype}}_inputted"> <span class="tag_object">{{tagname}}</span>{{-''-}} <button class="remove_tag_button red_button" onclick="remove_searchtag(this, '{{tagname}}', inputted_{{tagtype}});"></button> </li> {% endfor %} {% endif %} <li><input id="search_builder_{{tagtype}}_input" type="text"></li> </ul> </div> {% endfor %} <div id="search_builder_expression" {% if not search_kwargs["tag_expression"]%}style="display:none"{%endif%}> <span>Tag Expression:</span> <input id="search_builder_expression_input" name="tag_expression" type="text" {% if search_kwargs["tag_expression"] %} value="{{search_kwargs["tag_expression"]}}" {% endif %} > </div> <div id="search_builder_orderby"> <span>Order by</span> <ul id="search_builder_orderby_ul"> {% if "orderby" in search_kwargs and search_kwargs["orderby"] %} {% for orderby in search_kwargs["orderby"] %} {% set column, sorter=orderby.split("-") %} {{ create_orderby_li(selected_column=column, selected_sorter=sorter) }} {% endfor %} {% else %} {{ create_orderby_li(selected_column=0, selected_sorter=0) }} {% endif %} <li id="search_builder_orderby_newrow"><button class="green_button" onclick="add_new_orderby()">+</button></li> </ul> </div> <br> <form id="search_builder_form" action="" onsubmit="return submit_search();"> <span>Min-max values</span> <input type="text" class="basic_param" value="{%if search_kwargs['area']%}{{search_kwargs['area']}}{%endif%}" name="area" placeholder="Area: 1m-2m"> <input type="text" class="basic_param" value="{%if search_kwargs['width']%}{{search_kwargs['width']}}{%endif%}" name="width" placeholder="Width: 1k-2k"> <input type="text" class="basic_param" value="{%if search_kwargs['height']%}{{search_kwargs['height']}}{%endif%}" name="height" placeholder="Height: 1k-2k"> <input type="text" class="basic_param" value="{%if search_kwargs['ratio']%}{{search_kwargs['ratio']}}{%endif%}" name="ratio" placeholder="Aspect Ratio: 1.7-2"> <input type="text" class="basic_param" value="{%if search_kwargs['bytes']%}{{search_kwargs['bytes']}}{%endif%}" name="bytes" placeholder="File Size: 1mb-2mb"> <input type="text" class="basic_param" value="{%if search_kwargs['duration']%}{{search_kwargs['duration']}}{%endif%}" name="duration" placeholder="Duration: 10:00-20:00"> <input type="text" class="basic_param" value="{%if search_kwargs['created']%}{{search_kwargs['created']}}{%endif%}" name="created" placeholder="Created: 1483228800-1514764800"> <br> <span>Other filters</span> <input type="text" class="basic_param" value="{%if search_kwargs['filename']%}{{search_kwargs['filename']}}{%endif%}" name="filename" placeholder="Filename"> <input type="text" class="basic_param" value="{%if search_kwargs['mimetype']%}{{search_kwargs['mimetype']}}{%endif%}" name="mimetype" placeholder="Mimetype(s)"> <input type="text" class="basic_param" value="{%if search_kwargs['extension']%}{{search_kwargs['extension']}}{%endif%}" name="extension" placeholder="Extension(s)"> <input type="text" class="basic_param" value="{%if search_kwargs['extension_not']%}{{search_kwargs['extension_not']}}{%endif%}" name="extension_not" placeholder="Forbid extension(s)"> <select name="limit" class="basic_param"> {% set limit_options = [20, 50, 100] %} {% if search_kwargs['limit'] not in limit_options %} {% do limit_options.append(search_kwargs['limit']) %} {% do limit_options.sort() %} {% endif %} {% for limit_option in limit_options %} <option{{-' '-}} value="{{limit_option}}"{{-''-}} {{-" selected" if search_kwargs['limit'] == limit_option else ""-}} > {{- limit_option }} items{{-''-}} </option> {% endfor %} </select> <select name="has_tags" class="basic_param"> <option value="" {%if search_kwargs['has_tags'] == None %}selected{%endif%}>Tagged and untagged</option> <option value="yes"{%if search_kwargs['has_tags'] == True %}selected{%endif%}>Tagged only</option> <option value="no" {%if search_kwargs['has_tags'] == False %}selected{%endif%}>Untagged only</option> </select> <select name="view" class="basic_param"> <option value="grid" {%if search_kwargs['view'] == "grid" %}selected{%endif%}>Grid</option> <option value="list" {%if search_kwargs['view'] == "list" %}selected{%endif%}>List</option> </select> <button type="submit" id="search_go_button" class="green_button" value="">Search</button> </form> {% if total_tags %} <span>Tags on this page (click to join query):</span> <ul> {% for tag in total_tags %} <li>{{-tag_object.tag_object( tag, extra_classes="tags_on_this_page", link='void', qualified_name=False, with_alt_qualified_name=True, with_alt_description=True, )-}}</li> {% endfor %} </ul> {% endif %} </div> <div id="right"> <p>You got {{photos|length}} items</p> {{prev_next_buttons()}} <div id="search_results_holder"> {% for photo in photos %} {{photo_card.create_photo_card(photo, view=search_kwargs["view"])}} {% endfor %} </div> {{prev_next_buttons()}} </div> </div> {{clipboard_tray.clipboard_tray()}} </body> <script type="text/javascript"> /* These are defined so that we know we don't need to include them in the constructed search URL, keeping it more tidy. */ PARAM_DEFAULTS = { 'limit': 50, 'view': 'grid', } function add_searchtag(box, value, inputted_list, li_class) { /* Called by hitting Enter within a must/may/forbid field. Checks whether the tag exists and adds it to the query. */ if (box.offsetParent === null) { // The box is hidden probably because we're in Expression mode. return; } console.log("adding " + value); var already_have = false; // We're going to be doing some in-place splicing to remove, // so make a duplicate for iterating existing_tags = Array.from(inputted_list); for (var index = 0; index < existing_tags.length; index += 1) { existing_tag = existing_tags[index]; if (existing_tag == value) { already_have = true; } else if (existing_tag.startsWith(value + ".") || value.startsWith(existing_tag + ".")) { remove_searchtag(box, existing_tag, inputted_list); } } if (!already_have) { inputted_list.push(value); var new_li = document.createElement("li"); new_li.className = li_class; var new_span = document.createElement("span"); new_span.className = "tag_object"; new_span.innerHTML = value; var new_delbutton = document.createElement("button") new_delbutton.classList.add("remove_tag_button"); new_delbutton.classList.add("red_button"); new_delbutton.onclick = function(){remove_searchtag(new_delbutton, value, inputted_list)}; new_li.appendChild(new_span); new_li.appendChild(new_delbutton); box_li = box.parentElement; ul = box_li.parentElement; ul.insertBefore(new_li, box_li); } } function remove_searchtag(li_member, value, inputted_list) { /* Given a member of the same tag type as the one we intend to remove, find the tag of interest and remove it from both the DOM and the inputted_list. Sorry for the roundabout technique. */ console.log("removing " + value); var li = li_member.parentElement; var ul = li.parentElement; var lis = ul.children; //console.log(lis); for (var index = 0; index < lis.length; index += 1) { li = lis[index]; var span = li.children[0]; if (span.tagName != "SPAN") {continue} var tagname = span.innerHTML; if (tagname != value) {continue} ul.removeChild(li); splice_at = inputted_list.indexOf(tagname); if (splice_at == -1) {continue} inputted_list.splice(splice_at, 1); } } function add_new_orderby() { /* Called by the green + button */ var ul = document.getElementById("search_builder_orderby_ul"); if (ul.children.length >= 9) { /* 9 because there are only 9 sortable properties */ return; } var li = ul.children; li = li[li.length - 2]; var clone_children = true; var new_li = li.cloneNode(clone_children) var button = document.getElementById("search_builder_orderby_newrow"); ul.insertBefore(new_li, button); } function orderby_remove_hook(button) { /* Called by the red button next to orderby dropdowns */ var li = button.parentElement; var ul = li.parentElement; // 2 because keep 1 row and the adder button if (ul.children.length>2) { /* You can't remove the only one left */ ul.removeChild(li); } } function simplify_tagnames(tags) { var new_tags = []; for (var index = 0; index < tags.length; index += 1) { var tag = tags[index]; tag = tag.split("."); tag = tag[tag.length - 1]; new_tags.push(tag); } return new_tags; } function submit_search() { /* Gather up all the form data and tags and compose the search URL */ var url = window.location.origin + "/search"; var parameters = []; var has_tag_params = false; var musts = simplify_tagnames(inputted_musts).join(","); if (musts) {parameters.push("tag_musts=" + musts); has_tag_params=true;} var mays = simplify_tagnames(inputted_mays).join(","); if (mays) {parameters.push("tag_mays=" + mays); has_tag_params=true;} var forbids = simplify_tagnames(inputted_forbids).join(","); if (forbids) {parameters.push("tag_forbids=" + forbids); has_tag_params=true;} var expression = document.getElementsByName("tag_expression")[0].value; if (expression) { //expression = expression.replace(new RegExp(" ", 'g'), "-"); parameters.push("tag_expression=" + expression); has_tag_params=true; } var basic_inputs = document.getElementsByClassName("basic_param"); for (var index = 0; index < basic_inputs.length; index += 1) { var boxname = basic_inputs[index].name; var box = document.getElementsByName(boxname)[0]; var value = box.value; value = value.split("&").join("%26"); console.log(value); if (PARAM_DEFAULTS[boxname] == value) { // Don't clutter url with default values. continue; } if (boxname == "has_tags" && has_tag_params && value == "no") { /* The user wants untagged only, but has tags in the search boxes? Override to "tagged or untagged" and let the tag searcher handle it. */ value = ""; } if (value == "") { continue; } parameters.push(boxname + "=" + value); } orderby_rows = document.getElementsByClassName("search_builder_orderby_li"); orderby_params = []; for (var index = 0; index < orderby_rows.length; index += 1) { var row = orderby_rows[index]; var column = row.children[0].value; var sorter = row.children[1].value; orderby_params.push(column + "-" + sorter); } orderby_params = orderby_params.join(","); if (orderby_params && orderby_params != "created-desc") { // Don't clutter url with default of created-desc parameters.push("orderby=" + orderby_params); } if (parameters.length > 0) { parameters = parameters.join("&"); parameters = "?" + parameters; url = url + parameters; } console.log(url); window.location.href = url; return false; } function tags_on_this_page_hook() { /* This is hooked onto the tag objects listed under "Found on this page". Clicking them will add it to your current search query under Musts */ add_searchtag( input_musts, QUALNAME_MAP[this.innerHTML], inputted_musts, "search_builder_musts_inputted" ); return false; } function tag_input_hook(box, inputted_list, li_class) { /* Assigned to the input boxes for musts, mays, forbids. Hitting Enter will add the resovled tag to the search form. */ if (event.keyCode != 13) {return;} if (!box.value) {return;} var value = box.value.toLocaleLowerCase(); value = value.split("."); value = value[value.length-1]; value = value.split("+")[0]; value = value.replace(new RegExp(" ", 'g'), "_"); value = value.replace(new RegExp("-", 'g'), "_"); if (!(value in QUALNAME_MAP)) { return; } value = QUALNAME_MAP[value]; console.log(inputted_list); add_searchtag(box, value, inputted_list, li_class) box.value = ""; } QUALNAME_MAP = {{qualname_map|safe}}; var input_musts = document.getElementById("search_builder_musts_input"); var input_mays = document.getElementById("search_builder_mays_input"); var input_forbids = document.getElementById("search_builder_forbids_input"); var input_expression = document.getElementById("search_builder_expression_input"); /* Prefix the form with the parameters from last search */ var inputted_musts = []; var inputted_mays = []; var inputted_forbids = []; {% for tagtype in ["musts", "mays", "forbids"] %} {% set key="tag_" + tagtype %} {% if search_kwargs[key] %} {% for tagname in search_kwargs[key] %} inputted_{{tagtype}}.push("{{tagname}}"); {% endfor %} {% endif %} {% endfor %} /* Assign the click handler to "Tags on this page" results. */ var found_on_page = document.getElementsByClassName("tags_on_this_page"); for (var index = 0; index < found_on_page.length; index += 1) { var tag_object = found_on_page[index]; if (tag_object.tagName != "A") {continue} tag_object.onclick = tags_on_this_page_hook; } input_musts.addEventListener("keyup", function(){tag_input_hook(this, inputted_musts, "search_builder_musts_inputted")}); input_mays.addEventListener("keyup", function(){tag_input_hook(this, inputted_mays, "search_builder_mays_inputted")}); input_forbids.addEventListener("keyup", function(){tag_input_hook(this, inputted_forbids, "search_builder_forbids_inputted")}); bind_box_to_button(input_expression, document.getElementById("search_go_button")); </script> </html>