// ==UserScript== // @name DeviantArt Search Galleries and Favorites // @namespace http://tampermonkey.net/ // @version 0.3.1 // @description Creates a search function that works on artist galleries and favorites collections. Search by deviation title and artist name. Numerous sorting options. // @author corepower // @match https://www.deviantart.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=deviantart.com // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; ///////////////////////////////////////////////////////// ////// Default User Preference Variables ////// // Maximum search return: Maximum number of results that will tile after a search. This can be set as large as you like, but the page may slow down. // Possibly even consider lowering it if things are laggy while searching. let maximumsearchreturn = 1000; // Sort by: based on setting, search results will be sorted based on description in the value. The "sortoptions" array holds the possible options for sorting. // The "Default" option is based on date added to the collection. Matches DeviantArt default. const sortoptions = ["Default", "Post Date(Desc)", "Post Date(Asc)", "Title(Desc)", "Title(Asc)", "Artist(Desc)", "Artist(Asc)", "Favorites(Desc)", "Favorites(Asc)", "Views(Desc)", "Views(Asc)"]; let sortby = sortoptions[0]; // Pagination rate limit: the amount of time, in seconds to delay downloading gallery/collection page results during search indexing. // Can be set to 0 but if lots of large galleries are indexed, DeviantArt may automatically temp ban your account/IP. // Note: Multiple searches on a single page will not rerun the index, therefore unlimited searches may be run on a single page without consequence as long as the page is not navigated or refreshed. let paginationratelimit = 1.0; // Rate limit warning: If true, puts a warning message in the search output text while indexing about rate limits if the rate limit is under two seconds on a large search. // Set to false to disable warning. let ratelimitwarning = true; // Pivotal row height: height, in pixels, that the dynamic deviation tiles will be based around. Tiles will be this tall or larger. let pivotalrowheight = 280; //// End Default User Preference Variables ////// ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// ////// Desired Features List - NOT YET IMPLEMENTED ////// // These features may come in future versions. // * Navigate within DeviantArt and reset search capability based on new page (partially implemented) // * Dynamic tile visibility so that any number of search results can be efficiently displayed without slowing down the page. // * Dynamic re-tiling on viewport size change // * React framework tie-ins for things like element creation, event listeners, and deviation favoriting. This may not be feasible. I don't know React. ////// End Desired Features List ////// ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// ////// Other Constants ////// //Define constants for text style toggles const regularblacktextcolor = "black"; const regularwhitetextcolor = "rgb(242, 242, 242)"; const warningtextcolor = "orange"; const errortextcolor = "red"; // Preferred size: this is a named identifier for a size of DeviantArt generated thumbnails. Larger numbers are generally higher quality. // Set this variable based on your internet speed or personal preference. // Choose a value from amongst the following list: ["92S", "150", "200H", "300W", "375W", "414W", "250T", "350T","400T", "preview", "social_preview"] const preferredsize = "350T"; // Tile margin: margin between deviation tiles, in pixels. DeviantArt uses 4 pixels, don't know why you would change this but you can. const tilemargin = 4; // Debug: debug flag turns on various console outputs for debugging. Outputs may or may not make sense, they were added as needed. const debug = false; ////// End Other Constants ////// ///////////////////////////////////////////////////////// //Special event listener for React-style internal navigation //Credit to Raja Osama: https://rajaosama.me/blogs/detect-react-route-change-in-vanilla-js //Re-run entire script after this type of navigation let monitored_url = location.href; document.body.addEventListener('click', ()=>{ requestAnimationFrame(()=>{ if(monitored_url!==location.href){ let old_url = monitored_url; monitored_url = location.href //DEBUG if(debug && false) console.log('url changed to: ', monitored_url); //Check for specific case where navigation is between Galleries and Favorites on a single artist let old_url_parts = old_url.split("/"); let monitored_url_parts = monitored_url.split("/"); //Disregard these base path type of /artist if(old_url_parts.length < 5 || monitored_url_parts.length < 5) return; //Check for if navigations from gallery -> favourite or favourite -> gallery let is_old_url_favourite = old_url_parts[4] == "favourites"; let is_new_url_favourite = monitored_url_parts[4] == "favourites"; if( is_old_url_favourite ? !is_new_url_favourite : is_new_url_favourite ) { return; //TODO modify main script execution for this type of internal navigation then enable observer by uncommenting code below //set observer on this element to load // waitForElm('._3h7d3').then((elm) => { // if(debug && true) console.log('Element ._3h7d3 is ready'); // //executeScript(); // }); } else { //not a problem navigation, run script regularly executeScript(); } } }); }, true); //DEBUG if(debug && false) console.log("Entrypoint ready state: ", document.readyState); if(debug && false) document.addEventListener('readystatechange', () => console.log("Ready state change: ", document.readyState)); //Page needs to be fully loaded for script to work if(document.readyState == "loading" || document.readyState == "interactive") window.addEventListener('load',executeScript); else executeScript(); //Function wrapper for the entire script to enable document readiness check above, as well as for re-running after React-style navigation events function executeScript() { //DEBUG if(debug && false) console.log("Entered script execution."); if(debug && false) console.log("Internal entrypoint ready state: ", document.readyState); //Pull in existing user preference settings to globals, re-store to confirm all of them exist in localStorage. Use global variables for get() actions, update globals and localStorage with set() actions. getUserPreferencesFromLocalStorage(); //Only execute script on gallery pages, otherwise return immediately let pathparts = window.location.pathname.split("/"); if(pathparts.length < 3) { return; } if(pathparts[2] != "gallery" && pathparts[2] != "favourites") { return; } //Creating and adding this search element structure to the DOM //
// // // // // // // // //
let searchparentparent = document.getElementsByClassName("_3h7d3")[0]; let searchparent = document.createElement("div"); searchparent.className = "_1hkGk DRK5r _1bp4v"; searchparent.style.width = "350px"; let searchiconparentparent = document.createElement("span"); searchiconparentparent.className = "DLN_H"; let searchiconparent = document.createElement("span"); searchiconparent.className = "z8jNZ _1yoxj _2F1i2"; var searchicon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); searchicon.setAttribute("viewBox", "0 0 24 24"); var searchiconpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); searchiconpath.setAttribute("d", "M19 11a8 8 0 10-3.095 6.32l3.63 3.63.095.083a1 1 0 001.32-1.498l-3.63-3.63A7.965 7.965 0 0019 11zM5 11a6 6 0 1112 0 6 6 0 01-12 0z"); let searchelement = document.createElement('input'); searchelement.type = 'text'; searchelement.className = 'aFKMF _3kAA3 _2KZ9p'; searchelement.setAttribute('aria-invalid', 'false'); searchelement.id = 'search-gallery'; searchelement.autocomplete = 'off'; searchelement.placeholder = 'Search Gallery'; searchelement.value = ''; searchicon.appendChild(searchiconpath); searchiconparent.appendChild(searchicon); searchiconparentparent.appendChild(searchiconparent); searchparent.appendChild(searchiconparentparent); searchparent.appendChild(searchelement); //Create search results text output and put it next to the search box let searchoutputtext = document.createElement("span"); searchoutputtext.style.margin = "8px"; searchoutputtext.style.maxWidth = "1000px"; searchparentparent.appendChild(searchoutputtext); searchparentparent.appendChild(searchparent); //Create Settings Cog. Minified because we don't need access to any of the inner elements. let settingscogdiv = document.createElement("div"); settingscogdiv.style.position = "relative"; settingscogdiv.id = "settingscog"; settingscogdiv.innerHTML = '' //Create toggleable settings menu let settingsmenudiv = createSettingsMenu(); searchparentparent.appendChild(settingscogdiv); //Add event listeners searchelement.addEventListener("focus", buildIndex, {once : true}); searchelement.addEventListener("keyup", search); settingscogdiv.firstChild.addEventListener("click", toggleSettingsElement); document.addEventListener("click", hideSettingsElement); //define persistent globals for search indexing const csrftoken = window.__CSRF_TOKEN__; const artist_friendly_id = pathparts[1];//from the early URL path deconstruction let deviations;//this will be objects to index search with let matchingdeviations;//this will be objects that match the search let initialstatescripttext; let initialstatematch; let i = 0; while(initialstatematch == null) { initialstatescripttext = document.getElementsByTagName("script")[i].innerHTML; initialstatematch = initialstatescripttext.match(/\.__INITIAL_STATE__\s*=\s*JSON\.parse\(("[^\n]+")/); i++; } if(initialstatematch.length < 2) { console.error("DeviantArt Gallery Search Userscript Error: Could not parse script tag for __INITIAL_STATE__") searchelement.placeholder = 'Script Error'; return; } let initialstateJSON = JSON.parse(eval(initialstatematch[1])); //console.log(initialstateJSON); let isgallery = pathparts[2] == "gallery"; let collectionorgalleryfoldername = isgallery ? "galleryFolder" : "collectionFolder"; let foldersJSON = initialstateJSON["@@entities"][collectionorgalleryfoldername]; //console.log(foldersJSON); let isallfolder = false; if(pathparts.length >= 4 && pathparts[3] == "all") { isallfolder = true; } let folderid = "-1";// folder id of "-1" corresponds to "All" gallery let gallerysize = 0; if(!isallfolder) { //Get folder id from current url as most up-to-date source, else default assigned folder id from initial state //TODO this is imperfect. Fails if internal React navigation between galleries and favorites is initiated. if(pathparts.length >= 4) { folderid = pathparts[3]; } else {//Featured or imperfect navigation, for now just handle featured for(let folder in foldersJSON) { if(foldersJSON[folder].name == "Featured"){ folderid = folder; break; } } //folderid = initialstateJSON["gallectionSection"]["currentlyViewedFolderId"]; } } gallerysize = foldersJSON[folderid]["totalItemCount"]; //construct reuseable gallery pagination api string pieces let paginationstring_base = "https://www.deviantart.com/_napi/da-user-profile/api/" + (isgallery ? "gallery" : "collection") + "/contents?username=" + artist_friendly_id + "&csrf_token=" + csrftoken + (isallfolder ? "&all_folder=true" : "&folderid=" + folderid) + "&limit=60&offset="; //Get remaining UI elements needed for reference let itemscontainer = document.getElementsByClassName("RMUi2")[0]; let searchitemscontainer = itemscontainer.cloneNode(); itemscontainer.parentNode.appendChild(searchitemscontainer); //This function indexes the gallery to be searched on textbox focus async function buildIndex() { searchelement.placeholder = 'Building Search Index...'; searchelement.blur(); searchelement.setAttribute('readonly', 'readonly'); //DEBUG indexing performance let starttime; if(debug && true) { starttime = performance.now(); } let currentoffset = 0; let paginated_urls = []; while(currentoffset < gallerysize) { let currentpaginationurl = paginationstring_base + currentoffset; paginated_urls.push(currentpaginationurl); currentoffset += 60;//60 is maximum pagination } //DEBUG if(debug && false) console.log(paginated_urls); await concatenateJson(paginated_urls) .then(concatenatedJson => { //prepare indexable items by making them lowercase let lowercasejson = concatenatedJson; for(let i=0; i< lowercasejson.length; i++) { let deviation = lowercasejson[i].deviation; deviation.gs_username = deviation.author.username.toLowerCase(); deviation.gs_title = deviation.title.toLowerCase().trim(); deviation.gs_default_order = i; deviation.gs_favorites = deviation.stats.favourites; deviation.gs_views = deviation.stats.views; //Convert datetime to unix milliseconds and store const pubDate = new Date(deviation.publishedTime ) deviation.gs_published_time = pubDate.getTime(); //Collect static thumbnail meta info. easier to do once let thumbpathstring = ""; for(let i=0; i 30 && paginationratelimit < 2.0 && ratelimitwarning) { searchoutputtext.style.color = warningtextcolor; searchoutputtext.innerHTML = "This is a large gallery to search. If you run searches like this frequently, " + "consider raising the rate limit to 2 seconds or longer to avoid DeviantArt temp bans. " + "Rate limits can be changed and this warning can be turned off in the settings next to the search box."; } // Loop through the URLs for (let i=0; i 0 && waitmilliseconds != 0) { //DEBUG if(debug && false) console.log("Waiting " + waitmilliseconds + " milliseconds."); await new Promise(resolve => setTimeout(resolve, waitmilliseconds)); } //DEBUG if(debug && false) console.log("Download round " + i); // Download the current URL and concatenate the JSON result let response = await fetchJson(urls[i]); let json = response.results; results = results.concat(json); } //Reset output text after possible rate limit warning. searchoutputtext.style.color = regularwhitetextcolor; searchoutputtext.innerHTML = ""; return results; } //Searches through deviations array on keyup event function search() { let currentsearchtext = searchelement.value.toLowerCase(); //DEBUG if(debug && true) console.log("Searching: " + currentsearchtext); //Restore gallery visibility and abort if search goes inactive if(currentsearchtext == "") { itemscontainer.style.display = ""; searchitemscontainer.style.display = "none"; searchoutputtext.innerHTML = ""; return; } //hide original gallery, show custom search gallery itemscontainer.style.display = "none"; searchitemscontainer.style.display = ""; //search let resultsarray = []; for(let i=0; i< deviations.length; i++) { let deviation = deviations[i].deviation; if(deviation.gs_username.includes(currentsearchtext)) { resultsarray.push(deviation); continue; } if(deviation.gs_title.includes(currentsearchtext)) { resultsarray.push(deviation); continue; } } matchingdeviations = resultsarray; //Now display based on results if(resultsarray.length == 0) { searchoutputtext.innerHTML = "No results."; return; } //DEBUG if(debug && true) console.log("Results:", matchingdeviations); if(resultsarray.length > maximumsearchreturn) { searchoutputtext.innerHTML = resultsarray.length + " results. Too many for display. (Maximum is set to " + maximumsearchreturn + " results)"; return; } searchoutputtext.innerHTML = resultsarray.length + " result" + (resultsarray.length == 1 ? "." : "s."); assignTileDimensions(); tileElements(); //DEBUG sort order if(debug && false) { console.log("-----------") for(let i=0; i= viewportwidth) { break; } else { rowitemcount++; currentindex++; } //last row case detector if(currentindex == matchingdeviations.length) { isfinalrow = true; } } //now we have row item count, size to fill usable viewport width and then aspect for final row height //console.log("Row item count: ", rowitemcount); var useableviewportwidth = viewportwidth - (rowitemcount*tilemargin*2); //console.log("Useable viewport width: ", useableviewportwidth); currentindex = useditemscount;//reset to beginning of row index if(!isfinalrow)//perform sizing calculations on every row but the last { //get width ratio denominator var sumwidthratios = 0; for(let i=0; i", deviation.media.prettyName); let tokenstring = deviation.media.token == null ? "" : "?token=" + deviation.media.token[0]; let thumburl = deviation.media.baseUri + thumbpathstring + tokenstring; //Create Image DONE let imgelement = document.createElement("img"); imgelement.alt = deviation.title; imgelement.src = thumburl; imgelement.style.width = "100%"; imgelement.style.height = "100%"; imgelement.style.objectFit = "cover"; imgelement.style.objectPosition = "50% 100%"; //Image container Done let imgcontainer = document.createElement("div") imgcontainer.className = "_24Wda"; imgcontainer.style.width = "100%"; imgcontainer.style.height = "100%"; //Create link to deviation page Done let artlinkelement = document.createElement("a") artlinkelement.href = deviation.url; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; imgcontainer.appendChild(imgelement); artlinkelement.appendChild(imgcontainer); draggablecontainer.appendChild(artlinkelement); outermostdiv.appendChild(draggablecontainer); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; //Link with Title let arttitlelinkelement = document.createElement("a") arttitlelinkelement.href = deviation.url; arttitlelinkelement.className = "KoW6A"; //Title let arttitleelement = document.createElement("h2") arttitleelement.className = "_1lmpZ"; arttitleelement.innerHTML = deviation.title; arttitlelinkelement.appendChild(arttitleelement); titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } function createLiteratureDeviationElement(deviation) { let outermostdiv = document.createElement("div"); outermostdiv.style.width = deviation.gs_tile_width; outermostdiv.style.height = deviation.gs_tile_height; outermostdiv.style.display = "inline-block"; outermostdiv.style.float = "left"; outermostdiv.style.position = "relative"; outermostdiv.style.margin = tilemargin + "px"; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; //Section element that contains literature display elements let sectionelement = document.createElement("section"); sectionelement.className = "_33VtO _3rqZq"; sectionelement.style.width = "100%"; sectionelement.style.height = "100%"; //Fancy literature deviation background element container let literaturebgcontainer = document.createElement("div") literaturebgcontainer.className = "xPxyA LXVwg"; //Fancy literature deviation background (SVG) let litbgSVG = document.createElement("svg"); litbgSVG.setAttribute("viewBox", "0 0 15 12"); litbgSVG.setAttribute("height", "100%"); litbgSVG.setAttribute("preserveAspectRatio", "xMidYMin slice"); litbgSVG.setAttribute("fill-rule", "evenodd"); //Fancy literature deviation background (lineargradient) let litbglineargradient = document.createElement("linearGradient"); litbglineargradient.setAttribute("x1", "87.8481761%"); litbglineargradient.setAttribute("y1", "16.3690766%"); litbglineargradient.setAttribute("x2", "45.4107524%"); litbglineargradient.setAttribute("y2", "71.4898596%"); //Fancy literature deviation background (stop color)s let litbgstop1 = document.createElement("stop"); litbgstop1.setAttribute("stop-color", "#00FF62"); litbgstop1.setAttribute("offset", "0%"); let litbgstop2 = document.createElement("stop"); litbgstop2.setAttribute("stop-color", "#3197EF"); litbgstop2.setAttribute("offset", "100%"); litbgstop2.setAttribute("stop-opacity", "0"); litbglineargradient.appendChild(litbgstop1); litbglineargradient.appendChild(litbgstop2); litbgSVG.appendChild(litbglineargradient); literaturebgcontainer.appendChild(litbgSVG); sectionelement.appendChild(literaturebgcontainer); draggablecontainer.appendChild(sectionelement); outermostdiv.appendChild(draggablecontainer); //Literature Preview Subsection //Fancy literature deviation background element container let literaturecategorydiv = document.createElement("div") literaturecategorydiv.className = "_3hLq8"; literaturecategorydiv.innerHTML = "Literature"; //Literature Preview Title let literaturepreviewtitle = document.createElement("h2") literaturepreviewtitle.className = "_2mwJN"; literaturepreviewtitle.innerHTML = deviation.title; //Literature Preview Text let literaturepreviewtext = document.createElement("h2") literaturepreviewtext.className = "heXvc"; literaturepreviewtext.innerHTML = deviation.textContent.excerpt; sectionelement.appendChild(literaturecategorydiv); sectionelement.appendChild(literaturepreviewtitle); sectionelement.appendChild(literaturepreviewtext); //Deviation link section let deviationlink = document.createElement("a"); deviationlink.href = deviation.url; deviationlink.className = "_1vRyy"; draggablecontainer.appendChild(deviationlink); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; // //Link with Title // let arttitlelinkelement = document.createElement("a") // arttitlelinkelement.href = deviation.url; // arttitlelinkelement.className = "KoW6A"; // //Title // let arttitleelement = document.createElement("h2") // arttitleelement.className = "_1lmpZ"; // arttitleelement.innerHTML = deviation.title; // arttitlelinkelement.appendChild(arttitleelement); //titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } function createJournalDeviationElement(deviation) { let outermostdiv = document.createElement("div"); outermostdiv.style.width = deviation.gs_tile_width; outermostdiv.style.height = deviation.gs_tile_height; outermostdiv.style.display = "inline-block"; outermostdiv.style.float = "left"; outermostdiv.style.position = "relative"; outermostdiv.style.margin = tilemargin + "px"; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; //Section element that contains journal display elements let sectionelement = document.createElement("section"); sectionelement.className = "_1C7DQ _1L6MH"; sectionelement.style.width = "100%"; sectionelement.style.height = "100%"; //Journal title container let journaltitlecontainer = document.createElement("div") journaltitlecontainer.className = "_1i4Yb"; //Journal Title let journaltitle = document.createElement("h2") journaltitle.className = "mhmhR"; journaltitle.innerHTML = deviation.title; journaltitlecontainer.appendChild(journaltitle); sectionelement.appendChild(journaltitlecontainer); draggablecontainer.appendChild(sectionelement); outermostdiv.appendChild(draggablecontainer); //Journal Excerpt subsection //Journal preview container let journalpreviewcontainer = document.createElement("div") journalpreviewcontainer.className = "_2Hfrr"; //Journal datetime container let journaldtcontainer = document.createElement("div") journaldtcontainer.className = "uBAbQ"; //Journal datetime, also get the correct date format. Example: Dec 14, 2015 let journaldt = document.createElement("time") journaldt.dateTime = deviation.publishedTime; let date = new Date(deviation.publishedTime); let formattedDate = date.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric'}); journaldt.innerHTML = formattedDate; //Journal excerpt div let journalexcerptdiv = document.createElement("div") journalexcerptdiv.className = "legacy-journal _2HUtS"; journalexcerptdiv.innerHTML = deviation.textContent.excerpt; journaldtcontainer.appendChild(journaldt); journalpreviewcontainer.appendChild(journaldtcontainer); journalpreviewcontainer.appendChild(journalexcerptdiv); sectionelement.appendChild(journalpreviewcontainer); //Deviation link section let deviationlink = document.createElement("a"); deviationlink.href = deviation.url; deviationlink.className = "_1vRyy"; draggablecontainer.appendChild(deviationlink); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; // arttitlelinkelement.appendChild(arttitleelement); //titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } function toggleSettingsElement() { if(settingsmenudiv.style.display == "none") { settingsmenudiv.style.display = ""; settingsmenudiv.focus(); } else { settingsmenudiv.style.display = "none"; } } function hideSettingsElement(event) { if(!settingscogdiv.contains(event.target)){ settingsmenudiv.style.display = 'none'; } } function createSettingsMenu() { let settingscontainer = document.createElement("div"); settingscontainer.id = "settingsmenu"; settingscontainer.style.background = "#06070d"; settingscontainer.style.border = "1px solid #262830"; settingscontainer.style.borderRadius = "5px solid #262830"; settingscontainer.style.position = "absolute"; settingscontainer.style.right = "-20px"; settingscontainer.style.top = "40px"; settingscontainer.style.zIndex = "1"; settingscontainer.style.zIndex = "1"; settingscontainer.style.display = "none"; let settingstable = document.createElement("table"); settingstable.style.width = "300px"; settingstable.style.margin = "5px"; let tr1 = document.createElement("tr"); let tr2 = document.createElement("tr"); let tr3 = document.createElement("tr"); let tr4 = document.createElement("tr"); let t2tr1 = document.createElement("tr"); let maxsearchresultsleft = document.createElement("td"); maxsearchresultsleft.style.textAlign = "left"; maxsearchresultsleft.style.padding = "5px"; maxsearchresultsleft.innerHTML = "Max Search Results:"; let maxsearchresultsright = document.createElement("td"); maxsearchresultsright.style.textAlign = "right"; maxsearchresultsright.style.padding = "5px"; let maxsearchresultsinput = document.createElement("input"); maxsearchresultsinput.id = "maximumsearchreturn"; maxsearchresultsinput.type = "text"; maxsearchresultsinput.style.width = "40px"; maxsearchresultsinput.value = maximumsearchreturn; maxsearchresultsright.appendChild(maxsearchresultsinput); tr1.appendChild(maxsearchresultsleft); tr1.appendChild(maxsearchresultsright); let paginationrateleft = document.createElement("td"); paginationrateleft.style.textAlign = "left"; paginationrateleft.style.padding = "5px"; paginationrateleft.innerHTML = "Index Rate Limit (in seconds):"; let paginationrateright = document.createElement("td"); paginationrateright.style.textAlign = "right"; paginationrateright.style.padding = "5px"; let paginationrateinput = document.createElement("input"); paginationrateinput.id = "paginationratelimit"; paginationrateinput.type = "text"; paginationrateinput.style.width = "40px"; paginationrateinput.value = paginationratelimit.toPrecision(2); paginationrateright.appendChild(paginationrateinput); tr2.appendChild(paginationrateleft); tr2.appendChild(paginationrateright); let ratewarningleft = document.createElement("td"); ratewarningleft.style.textAlign = "left"; ratewarningleft.style.padding = "5px"; ratewarningleft.innerHTML = "Rate Limit Warning:"; let ratewarningright = document.createElement("td"); ratewarningright.style.textAlign = "right"; ratewarningright.style.padding = "5px"; let ratewarningcheckbox = document.createElement("input"); ratewarningcheckbox.id = "ratelimitwarning"; ratewarningcheckbox.type = "checkbox"; ratewarningcheckbox.style.accentColor = "green"; ratewarningcheckbox.checked = ratelimitwarning; ratewarningright.appendChild(ratewarningcheckbox); tr3.appendChild(ratewarningleft); tr3.appendChild(ratewarningright); let tileheightleft = document.createElement("td"); tileheightleft.style.textAlign = "left"; tileheightleft.style.padding = "5px"; tileheightleft.innerHTML = "Rough Tiling Height (in pixels):"; let tileheightright = document.createElement("td"); tileheightright.style.textAlign = "right"; tileheightright.style.padding = "5px"; let tileheightinput = document.createElement("input"); tileheightinput.id = "pivotalrowheight"; tileheightinput.type = "text"; tileheightinput.style.width = "40px"; tileheightinput.value = pivotalrowheight; tileheightright.appendChild(tileheightinput); tr4.appendChild(tileheightleft); tr4.appendChild(tileheightright); settingstable.appendChild(tr1); settingstable.appendChild(tr2); settingstable.appendChild(tr3); settingstable.appendChild(tr4); settingscontainer.appendChild(settingstable); let settingstable2 = document.createElement("table"); settingstable2.style.width = "300px"; settingstable2.style.margin = "5px"; let sortbyleft = document.createElement("td"); sortbyleft.style.textAlign = "left"; sortbyleft.style.padding = "5px"; sortbyleft.innerHTML = "Sort By:"; let sortbyright = document.createElement("td"); sortbyright.style.textAlign = "right"; let sortbycontainer = document.createElement("div"); sortbycontainer.style.width = "180px"; sortbycontainer.style.color = regularwhitetextcolor; sortbycontainer.style.marginLeft = "auto"; sortbycontainer.style.display = "flex"; let leftsort = document.createElement("span"); leftsort.id = "leftsort"; leftsort.style.cursor = "pointer"; leftsort.style.margin = "5px auto"; leftsort.style.background = "green"; leftsort.style.padding = "5px"; leftsort.style.borderTopLeftRadius = "5px"; leftsort.style.borderBottomLeftRadius = "5px"; leftsort.innerHTML = "<"; let sorttext = document.createElement("span"); sorttext.id = "sortby"; sorttext.style.margin = "5px auto"; sorttext.style.background = "green"; sorttext.style.padding = "5px"; sorttext.style.width = "120px"; sorttext.style.textAlign = "center"; sorttext.innerHTML = sortby; let rightsort = document.createElement("span"); rightsort.id = "rightsort"; rightsort.style.cursor = "pointer"; rightsort.style.margin = "5px auto"; rightsort.style.background = "green"; rightsort.style.padding = "5px"; rightsort.style.borderTopRightRadius = "5px"; rightsort.style.borderBottomRightRadius = "5px"; rightsort.innerHTML = ">"; sortbycontainer.appendChild(leftsort); sortbycontainer.appendChild(sorttext); sortbycontainer.appendChild(rightsort); sortbyright.appendChild(sortbycontainer); t2tr1.appendChild(sortbyleft); t2tr1.appendChild(sortbyright); settingstable2.appendChild(t2tr1); settingscontainer.appendChild(settingstable2); settingscogdiv.appendChild(settingscontainer); //Add button animations leftsort.addEventListener("mouseover", hoverFade); leftsort.addEventListener("mouseout", hoverFadeCancel); rightsort.addEventListener("mouseover", hoverFade); rightsort.addEventListener("mouseout", hoverFadeCancel); //Add user settings change listeners maxsearchresultsinput.addEventListener("keyup", updateUserNumberPreference); paginationrateinput.addEventListener("keyup", updateUserNumberPreference); ratewarningcheckbox.addEventListener("click", updateUserCheckboxPreference); tileheightinput.addEventListener("keyup", updateUserNumberPreference); leftsort.addEventListener("click", updateSortByPreference); rightsort.addEventListener("click", updateSortByPreference); return settingscontainer; function hoverFade(event) { event.target.style.opacity = "50%"; } function hoverFadeCancel(event) { event.target.style.opacity = "100%"; } function updateUserNumberPreference(event) { let key = event.target.id; let value = event.target.value; let updatedCorrectly = setUserPreference(key, value); if(updatedCorrectly) { event.target.style.color = regularblacktextcolor; searchoutputtext.style.color = regularwhitetextcolor; if(searchoutputtext.innerHTML.includes("Error")) searchoutputtext.innerHTML = ""; } else { event.target.style.color = errortextcolor; searchoutputtext.style.color = errortextcolor; let typeproblemstring = (key == "paginationratelimit" ? "decimal number. (Example: 2.5)" : "integer. (Example: 300)"); searchoutputtext.innerHTML = "Error: '" + value + "' is not a " + typeproblemstring; } //Add an immediate tiling update if the desired row height is changed, handled within the search function if(key == "pivotalrowheight") { search(); } } function updateUserCheckboxPreference(event) { let key = event.target.id; let value = event.target.checked; setUserPreference(key, value); } function updateSortByPreference(event) { let isleft = event.target.id == "leftsort"; //Get and set new sort option based on click direction let sortoptionindex = 0; for(let i=0; i "1000" ) and direct inputs (ex: maximumsearchreturn -> 1000 ) if(key == "maximumsearchreturn") { let newmaximumsearchreturn = parseInt(value); if(isNaN(newmaximumsearchreturn)) return false; maximumsearchreturn = newmaximumsearchreturn; localStorage.setItem("maximumsearchreturn", maximumsearchreturn); } else if(key == "paginationratelimit") { let newpaginationratelimit = parseFloat(value); if(isNaN(newpaginationratelimit)) return false; paginationratelimit = newpaginationratelimit; localStorage.setItem("paginationratelimit", paginationratelimit); } else if(key == "ratelimitwarning") { let newratelimitwarning = (value == true || value == "true"); ratelimitwarning = newratelimitwarning; localStorage.setItem("ratelimitwarning", ratelimitwarning); } else if(key == "pivotalrowheight") { let newpivotalrowheight = parseInt(value); if(isNaN(newpivotalrowheight)) return false; pivotalrowheight = newpivotalrowheight; localStorage.setItem("pivotalrowheight", pivotalrowheight); } else if(key == "sortby") { sortby = value; localStorage.setItem("sortby", sortby); } else { return false; } return true; } ///////////////////////////////////////////////////////////////////////////////////// ///// Other Helper Functions ///// ///////////////////////////////////////////////////////////////////////////////////// function getAspectedWidth(origwidth, origheight, sizedheight) { return (origwidth * sizedheight) / origheight; } function getAspectedHeight(origwidth, origheight, sizedwidth) { return (origheight * sizedwidth) / origwidth; } //Function to sort deviationJSON based on attribute provided function sortDeviationsByKey(array, key, ascending) { //spot check for the variable type let spotcheck = typeof array[0].deviation[key]; if(spotcheck == "string") { return array.sort(function(a, b) { let x = a.deviation[key]; let y = b.deviation[key]; if(ascending) return x.localeCompare(y); else return y.localeCompare(x); }); } else if(spotcheck == "number") { return array.sort(function(a, b) { let x = a.deviation[key]; let y = b.deviation[key]; if(x == y) return 0; let returnval; if(x > y) returnval = 1; else returnval = -1; if(ascending) return returnval; else return -returnval; }); } else { console.error("Unhandled type for sort key: " + key); } } } //DOM Element Mutation Observer //Credit to Yong Wang on StackOverflow: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } })();