mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2025-06-21 18:31:28 +02:00
Merge Improvements from JeLuF:gallery
This commit is contained in:
commit
fb031a4c46
@ -740,3 +740,30 @@ croppr.js is licensed under the MIT license:
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
Masonry
|
||||||
|
=======
|
||||||
|
https://masonry.desandro.com/
|
||||||
|
|
||||||
|
Masonry is licensed under the MIT license:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2023 David DeSandro
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the “Software”), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
@ -110,19 +110,6 @@ def init():
|
|||||||
images = images.offset(page*images_per_page).limit(images_per_page)
|
images = images.offset(page*images_per_page).limit(images_per_page)
|
||||||
return images.all()
|
return images.all()
|
||||||
|
|
||||||
@server_api.get("/single_image")
|
|
||||||
def get_single_image(image_path: str, db: Session = Depends(get_db)):
|
|
||||||
from easydiffusion.easydb.mappings import GalleryImage
|
|
||||||
image_path = str(abspath(image_path))
|
|
||||||
try:
|
|
||||||
image: GalleryImage = db.query(GalleryImage).filter(GalleryImage.path == image_path).first()
|
|
||||||
head = "<head><link rel='stylesheet' href='/media/css/single-gallery.css'></head>"
|
|
||||||
body = f"<body><div><button id='use_these_settings' class='primaryButton' json='{image.settingsJSON()}'>Use these settings</button><button id='use_as_input' class='primaryButton' disabled>Use as Input</button></div><img src='/image/" + image.path + "'>" + image.htmlForm() + "</body>"
|
|
||||||
return Response(content="<html>" + head + body + "</head>", media_type="text/html")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
raise HTTPException(status_code=404, detail="Image not found")
|
|
||||||
|
|
||||||
def get_filename_from_url(url):
|
def get_filename_from_url(url):
|
||||||
path = urlparse(url).path
|
path = urlparse(url).path
|
||||||
name = path[path.rfind('/')+1:]
|
name = path[path.rfind('/')+1:]
|
||||||
|
@ -143,6 +143,10 @@ def init():
|
|||||||
def read_root():
|
def read_root():
|
||||||
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
|
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
|
||||||
|
|
||||||
|
@server_api.get("/gallery-image.html")
|
||||||
|
def read_gallery_image():
|
||||||
|
return FileResponse(os.path.join(app.SD_UI_DIR, "gallery-image.html"), headers=NOCACHE_HEADERS)
|
||||||
|
|
||||||
@server_api.on_event("shutdown")
|
@server_api.on_event("shutdown")
|
||||||
def shutdown_event(): # Signal render thread to close on shutdown
|
def shutdown_event(): # Signal render thread to close on shutdown
|
||||||
task_manager.current_state_error = SystemExit("Application shutting down.")
|
task_manager.current_state_error = SystemExit("Application shutting down.")
|
||||||
|
14
ui/gallery-image.html
Normal file
14
ui/gallery-image.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/media/css/single-gallery.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<button id="use_these_settings" class="primaryButton">Use these settings</button>
|
||||||
|
<button id="use_as_input" class="primaryButton">Use as Input</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img id="focusimg">
|
||||||
|
<div id="focusbox" class="panel-box">
|
||||||
|
</div>
|
||||||
|
</body></html>
|
@ -26,6 +26,7 @@
|
|||||||
<script src="/media/js/FileSaver.min.js"></script>
|
<script src="/media/js/FileSaver.min.js"></script>
|
||||||
<script src="/media/js/marked.min.js"></script>
|
<script src="/media/js/marked.min.js"></script>
|
||||||
<script src="/media/js/croppr.js"></script>
|
<script src="/media/js/croppr.js"></script>
|
||||||
|
<script src="/media/js/masonry.pkgd.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
@ -523,12 +524,26 @@
|
|||||||
<input id="gallery-model-search" type="text" onkeydown="gallery_keyDown_handler(event)" placeholder="Search for a model..."></input>
|
<input id="gallery-model-search" type="text" onkeydown="gallery_keyDown_handler(event)" placeholder="Search for a model..."></input>
|
||||||
<label for="gallery-page">Page:</label>
|
<label for="gallery-page">Page:</label>
|
||||||
<input id="gallery-page" name="Page" value="0" size="1" onkeypress="gallery_keyDown_handler(event)">
|
<input id="gallery-page" name="Page" value="0" size="1" onkeypress="gallery_keyDown_handler(event)">
|
||||||
|
<input id="gallery-thumbnail-size" name="gallery-thumbnail-size" class="editor-slider" type="range" value="5.5" min="3" max="20" step="0.05">
|
||||||
<button class="primaryButton" id="gallery-refresh" onclick="refreshGallery(true)">Load</button>
|
<button class="primaryButton" id="gallery-refresh" onclick="refreshGallery(true)">Load</button>
|
||||||
<button class="primaryButton" onclick="incrementGalleryPage()"><i class="fa-solid fa-arrow-right"></i></button>
|
<button class="primaryButton" onclick="incrementGalleryPage()"><i class="fa-solid fa-arrow-right"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<div class="gallery-container" id="imagecontainer"></div>
|
<div class="gallery-container" id="imagecontainer"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<dialog id="gallery-imginfo">
|
||||||
|
<div id="gallery-imginfo-header" class="dialog-header">
|
||||||
|
<div id="gallery-imginfo-header-left" class="dialog-header-left">
|
||||||
|
<h4>Image Settings</h4>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div id="gallery-imginfo-header-right">
|
||||||
|
<i id="gallery-imginfo-close-button" class="fa-solid fa-xmark fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="gallery-imginfo-content">
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1889,6 +1889,8 @@ div#enlarge-buttons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Gallery CSS */
|
/* Gallery CSS */
|
||||||
|
|
||||||
|
/*
|
||||||
.gallery {
|
.gallery {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -1896,6 +1898,7 @@ div#enlarge-buttons {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#gallery-search {
|
#gallery-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1909,29 +1912,32 @@ div#enlarge-buttons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gallery-container {
|
.gallery-container {
|
||||||
columns: 5 ;
|
|
||||||
column-gap: 1.5rem;
|
|
||||||
width: 95%;
|
|
||||||
margin: 0 0 ;
|
|
||||||
}
|
|
||||||
.gallery-container div {
|
|
||||||
margin: 0 1.5rem 1.5rem 0;
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
|
|
||||||
transition: all .75s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-container div img {
|
.gallery-image img {
|
||||||
width: 100%;
|
width: var(--gallery-width);
|
||||||
border-radius: 5px;
|
|
||||||
transition: all .25s ease-in-out;
|
|
||||||
box-shadow: 5px 5px 5px rgba(0,0,0,0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-container div img:hover {
|
.gallery-image {
|
||||||
box-shadow: 1px 1px 15px rgba(32,0,128,0.8);
|
position: relative;
|
||||||
|
width: var(--gallery-width);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-image-hover {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
opacity: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-image-hover button {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-image:hover .gallery-image-hover {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"tree_toggle",
|
"tree_toggle",
|
||||||
"json_toggle",
|
"json_toggle",
|
||||||
"extract_lora_from_prompt",
|
"extract_lora_from_prompt",
|
||||||
|
"gallery-thumbnail-size",
|
||||||
"embedding-card-size-selector",
|
"embedding-card-size-selector",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -142,6 +142,9 @@ let embeddingsSearchBox = document.querySelector("#embeddings-search-box")
|
|||||||
let embeddingsList = document.querySelector("#embeddings-list")
|
let embeddingsList = document.querySelector("#embeddings-list")
|
||||||
let embeddingsModeField = document.querySelector("#embeddings-mode")
|
let embeddingsModeField = document.querySelector("#embeddings-mode")
|
||||||
let embeddingsCardSizeSelector = document.querySelector("#embedding-card-size-selector")
|
let embeddingsCardSizeSelector = document.querySelector("#embedding-card-size-selector")
|
||||||
|
let galleryImginfoDialog = document.querySelector("#gallery-imginfo")
|
||||||
|
let galleryThumbnailSize = document.querySelector("#gallery-thumbnail-size")
|
||||||
|
let galleryImginfoDialogContent = document.querySelector("#gallery-imginfo-content")
|
||||||
|
|
||||||
let positiveEmbeddingText = document.querySelector("#positive-embedding-text")
|
let positiveEmbeddingText = document.querySelector("#positive-embedding-text")
|
||||||
let negativeEmbeddingText = document.querySelector("#negative-embedding-text")
|
let negativeEmbeddingText = document.querySelector("#negative-embedding-text")
|
||||||
@ -3085,40 +3088,186 @@ let recentResolutionsValues = []
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
/* Gallery JS */
|
/* Gallery JS */
|
||||||
|
|
||||||
|
const IMAGE_INFO = {
|
||||||
|
"Prompt": "prompt",
|
||||||
|
"Negative Prompt": "negative_prompt",
|
||||||
|
"Seed": "seed",
|
||||||
|
"Time": "time_created",
|
||||||
|
"Model": "use_stable_diffusion_model",
|
||||||
|
"VAE Model": "use_vae_model",
|
||||||
|
"Hypernetwork": "use_hypernetwork_model",
|
||||||
|
"LORA": "lora",
|
||||||
|
"Path": "path",
|
||||||
|
"Width": "width",
|
||||||
|
"Height": "height",
|
||||||
|
"Steps": "num_inference_steps",
|
||||||
|
"Sampler": "sampler_name",
|
||||||
|
"Guidance Scale": "guidance_scale",
|
||||||
|
"Tiling": "tiling",
|
||||||
|
"Upscaler": "use_upscale",
|
||||||
|
"Face Correction": "use_face_correction",
|
||||||
|
"Clip Skip": "clip_skip",
|
||||||
|
}
|
||||||
|
|
||||||
|
const IGNORE_TOKENS = ["None", "none", "Null", "null", "false", "False", null]
|
||||||
|
|
||||||
function galleryImage(item) {
|
function galleryImage(item) {
|
||||||
|
function galleryDetailTable(table) {
|
||||||
|
for (const [label, key] of Object.entries(IMAGE_INFO)) {
|
||||||
|
if (IGNORE_TOKENS.findIndex( k => (k == item[key])) == -1) {
|
||||||
|
let data = item[key]
|
||||||
|
if (key == "path") {
|
||||||
|
data = "…/"+data.split(/[\/\\]/).pop()
|
||||||
|
}
|
||||||
|
table.appendChild(htmlToElement(`<tr><th style="text-align: right;opacity:0.7;">${label}:</th><td>${data}</td></tr>`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let div = document.createElement("div")
|
let div = document.createElement("div")
|
||||||
let img = document.createElement("img")
|
div.classList.add("gallery-image")
|
||||||
|
|
||||||
|
let img = createElement("img", { style: "cursor: zoom-in;", src: "/image/" + item.path}, undefined, undefined)
|
||||||
|
img.dataset["request"] = JSON.stringify(item)
|
||||||
|
|
||||||
img.addEventListener("click", (event) => {
|
img.addEventListener("click", (event) => {
|
||||||
let w;
|
let w = window.open("/gallery-image.html")
|
||||||
w = window.open("/single_image?image_path=" + item.path, "_blank")
|
|
||||||
w.addEventListener("DOMContentLoaded", () => {
|
w.addEventListener("DOMContentLoaded", () => {
|
||||||
w.document.getElementsByTagName("body")[0].classList.add(themeField.value)
|
let fimg = w.document.getElementById("focusimg")
|
||||||
|
fimg.src = img.src
|
||||||
|
|
||||||
|
w.document.body.classList.add(themeField.value)
|
||||||
w.document.getElementById("use_these_settings").addEventListener("click", () => {
|
w.document.getElementById("use_these_settings").addEventListener("click", () => {
|
||||||
restoreTaskToUI(JSON.parse(w.document.getElementById("use_these_settings").getAttribute("json")))
|
restoreTaskToUI({
|
||||||
|
batchCount: 1,
|
||||||
|
numOutputsTotal: 1,
|
||||||
|
reqBody: item
|
||||||
|
})
|
||||||
})
|
})
|
||||||
w.document.getElementById("use_as_input").addEventListener("click", () => {
|
w.document.getElementById("use_as_input").addEventListener("click", () => {
|
||||||
alert("use as input")
|
onUseURLasInput(img.src)
|
||||||
|
showToast("Loaded image as EasyDiffusion input image", 5000, false, w.document)
|
||||||
})
|
})
|
||||||
|
let table = w.document.createElement("table")
|
||||||
|
galleryDetailTable(table)
|
||||||
|
w.document.getElementById("focusbox").replaceChildren(table)
|
||||||
|
document.dispatchEvent(new CustomEvent("newGalleryWindow", { detail: w }))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
img.src = "/image/" + item.path
|
let hover = document.createElement("div")
|
||||||
img.dataset["request"] = JSON.stringify(item)
|
hover.classList.add("gallery-image-hover")
|
||||||
div.appendChild(img)
|
|
||||||
|
let infoBtn = document.createElement("button")
|
||||||
|
infoBtn.classList.add("tertiaryButton")
|
||||||
|
infoBtn.innerHTML = '<i class="fa-regular fa-file-lines"></i>'
|
||||||
|
infoBtn.addEventListener("click", function() {
|
||||||
|
let table = document.createElement("table")
|
||||||
|
|
||||||
|
galleryDetailTable(table)
|
||||||
|
|
||||||
|
galleryImginfoDialogContent.replaceChildren(table)
|
||||||
|
galleryImginfoDialog.showModal()
|
||||||
|
})
|
||||||
|
hover.appendChild(infoBtn)
|
||||||
|
|
||||||
|
|
||||||
|
let imageExpandBtn=createElement("button", { style: "margin-left: 0.2em;"}, ["tertiaryButton"], undefined)
|
||||||
|
imageExpandBtn.innerHTML = '<i class="fa-solid fa-expand"></i>'
|
||||||
|
imageExpandBtn.addEventListener("click", function() {
|
||||||
|
function previousImage(img) {
|
||||||
|
const allImages = Array.from(document.getElementById("imagecontainer").querySelectorAll(".gallery-image img"))
|
||||||
|
const index = allImages.indexOf(img)
|
||||||
|
return allImages.slice(0, index).reverse()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextImage(img) {
|
||||||
|
const allImages = Array.from(document.getElementById("imagecontainer").querySelectorAll(".gallery-image img"))
|
||||||
|
const index = allImages.indexOf(img)
|
||||||
|
return allImages.slice(index + 1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageModalParameter(img) {
|
||||||
|
const previousImg = previousImage(img)
|
||||||
|
const nextImg = nextImage(img)
|
||||||
|
|
||||||
|
return {
|
||||||
|
src: img.src,
|
||||||
|
previous: previousImg ? () => imageModalParameter(previousImg) : undefined,
|
||||||
|
next: nextImg ? () => imageModalParameter(nextImg) : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageModal(imageModalParameter(img))
|
||||||
|
})
|
||||||
|
|
||||||
|
hover.appendChild(imageExpandBtn)
|
||||||
|
|
||||||
|
let openInNewTabBtn = document.createElement("button")
|
||||||
|
openInNewTabBtn.classList.add("tertiaryButton")
|
||||||
|
openInNewTabBtn.innerHTML = '<i class="fa-solid fa-arrow-up-right-from-square"></i>'
|
||||||
|
openInNewTabBtn.style["margin-left"] = "0.2em"
|
||||||
|
openInNewTabBtn.addEventListener("click", (e) => {
|
||||||
|
window.open(img.src)
|
||||||
|
})
|
||||||
|
hover.appendChild(openInNewTabBtn)
|
||||||
|
|
||||||
|
hover.appendChild(document.createElement("br"))
|
||||||
|
|
||||||
|
let useAsInputBtn = createElement("button", {}, ["tertiaryButton"], "Use as Input")
|
||||||
|
useAsInputBtn.addEventListener("click", (e) => {
|
||||||
|
onUseURLasInput(img.src)
|
||||||
|
showToast("Loaded image as input image")
|
||||||
|
})
|
||||||
|
|
||||||
|
hover.appendChild(useAsInputBtn)
|
||||||
|
|
||||||
|
div.replaceChildren(img, hover)
|
||||||
return div
|
return div
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUseURLasInput(url) {
|
||||||
|
toDataURL(url, blob => {
|
||||||
|
onUseAsInputClick(null, {src:blob})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
modalDialogCloseOnBackdropClick(galleryImginfoDialog)
|
||||||
|
makeDialogDraggable(galleryImginfoDialog)
|
||||||
|
|
||||||
|
galleryImginfoDialog.querySelector("#gallery-imginfo-close-button").addEventListener("click", () => {
|
||||||
|
galleryImginfoDialog.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
galleryThumbnailSize.addEventListener("input", layoutGallery)
|
||||||
|
window.addEventListener("resize", layoutGallery)
|
||||||
|
|
||||||
|
function layoutGallery() {
|
||||||
|
let container = document.getElementById("imagecontainer")
|
||||||
|
let thumbSize = parseFloat(galleryThumbnailSize.value)
|
||||||
|
thumbSize = (10*thumbSize*thumbSize)>>>0
|
||||||
|
let root = document.querySelector(':root')
|
||||||
|
root.style.setProperty('--gallery-width', thumbSize + "px")
|
||||||
|
let msnry = new Masonry( container, {
|
||||||
|
gutter: 10,
|
||||||
|
itemSelector: '.gallery-image',
|
||||||
|
columnWidth: thumbSize,
|
||||||
|
fitWidth: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function refreshGallery(newsearch = false) {
|
function refreshGallery(newsearch = false) {
|
||||||
if (newsearch) {
|
if (newsearch) {
|
||||||
document.getElementById("gallery-page").value = 0
|
document.getElementById("gallery-page").value = 0
|
||||||
}
|
}
|
||||||
let container = document.getElementById("imagecontainer")
|
let container = document.getElementById("imagecontainer")
|
||||||
|
container.innerHTML = ""
|
||||||
let params = new URLSearchParams({
|
let params = new URLSearchParams({
|
||||||
prompt: document.getElementById("gallery-prompt-search").value,
|
prompt: document.getElementById("gallery-prompt-search").value,
|
||||||
model: document.getElementById("gallery-model-search").value,
|
model: document.getElementById("gallery-model-search").value,
|
||||||
page: document.getElementById("gallery-page").value
|
page: document.getElementById("gallery-page").value
|
||||||
})
|
})
|
||||||
container.innerHTML = ""
|
|
||||||
fetch('/all_images?' + params)
|
fetch('/all_images?' + params)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
@ -3130,10 +3279,31 @@ function refreshGallery(newsearch = false) {
|
|||||||
json.forEach(item => {
|
json.forEach(item => {
|
||||||
container.appendChild(galleryImage(item))
|
container.appendChild(galleryImage(item))
|
||||||
})
|
})
|
||||||
|
// Wait for all images to be loaded
|
||||||
|
Promise.all(Array.from(container.querySelectorAll("img")).map(img => {
|
||||||
|
if (img.complete)
|
||||||
|
{
|
||||||
|
return Promise.resolve(img.naturalHeight !== 0)
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
img.addEventListener('load', () => resolve(true))
|
||||||
|
img.addEventListener('error', () => resolve(false))
|
||||||
|
})
|
||||||
|
})).then(results => {
|
||||||
|
// then layout the images
|
||||||
|
layoutGallery()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
document.getElementById("gallery-refresh").innerText = "Refresh"
|
document.getElementById("gallery-refresh").innerText = "Refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener("tabClick", (e) => {
|
||||||
|
if (e.detail.name == 'gallery') {
|
||||||
|
refreshGallery()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
function decrementGalleryPage() {
|
function decrementGalleryPage() {
|
||||||
let page = Math.max(document.getElementById("gallery-page").value - 1, 0)
|
let page = Math.max(document.getElementById("gallery-page").value - 1, 0)
|
||||||
document.getElementById("gallery-page").value = page
|
document.getElementById("gallery-page").value = page
|
||||||
|
2504
ui/media/js/masonry.pkgd.js
Normal file
2504
ui/media/js/masonry.pkgd.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -872,19 +872,19 @@ function createTab(request) {
|
|||||||
|
|
||||||
|
|
||||||
/* TOAST NOTIFICATIONS */
|
/* TOAST NOTIFICATIONS */
|
||||||
function showToast(message, duration = 5000, error = false) {
|
function showToast(message, duration = 5000, error = false, doc=document) {
|
||||||
const toast = document.createElement("div")
|
const toast = doc.createElement("div")
|
||||||
toast.classList.add("toast-notification")
|
toast.classList.add("toast-notification")
|
||||||
if (error === true) {
|
if (error === true) {
|
||||||
toast.classList.add("toast-notification-error")
|
toast.classList.add("toast-notification-error")
|
||||||
}
|
}
|
||||||
toast.innerHTML = message
|
toast.innerHTML = message
|
||||||
document.body.appendChild(toast)
|
doc.body.appendChild(toast)
|
||||||
|
|
||||||
// Set the position of the toast on the screen
|
// Set the position of the toast on the screen
|
||||||
const toastCount = document.querySelectorAll(".toast-notification").length
|
const toastCount = doc.querySelectorAll(".toast-notification").length
|
||||||
const toastHeight = toast.offsetHeight
|
const toastHeight = toast.offsetHeight
|
||||||
const previousToastsHeight = Array.from(document.querySelectorAll(".toast-notification"))
|
const previousToastsHeight = Array.from(doc.querySelectorAll(".toast-notification"))
|
||||||
.slice(0, -1) // exclude current toast
|
.slice(0, -1) // exclude current toast
|
||||||
.reduce((totalHeight, toast) => totalHeight + toast.offsetHeight + 10, 0) // add 10 pixels for spacing
|
.reduce((totalHeight, toast) => totalHeight + toast.offsetHeight + 10, 0) // add 10 pixels for spacing
|
||||||
toast.style.bottom = `${10 + previousToastsHeight}px`
|
toast.style.bottom = `${10 + previousToastsHeight}px`
|
||||||
@ -896,7 +896,7 @@ function showToast(message, duration = 5000, error = false) {
|
|||||||
const removeTimeoutId = setTimeout(() => {
|
const removeTimeoutId = setTimeout(() => {
|
||||||
toast.remove()
|
toast.remove()
|
||||||
// Adjust the position of remaining toasts
|
// Adjust the position of remaining toasts
|
||||||
const remainingToasts = document.querySelectorAll(".toast-notification")
|
const remainingToasts = doc.querySelectorAll(".toast-notification")
|
||||||
const removedToastBottom = toast.getBoundingClientRect().bottom
|
const removedToastBottom = toast.getBoundingClientRect().bottom
|
||||||
|
|
||||||
remainingToasts.forEach((toast) => {
|
remainingToasts.forEach((toast) => {
|
||||||
@ -908,13 +908,13 @@ function showToast(message, duration = 5000, error = false) {
|
|||||||
// Wait for the slide-down animation to complete
|
// Wait for the slide-down animation to complete
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Remove the slide-down class after the animation has completed
|
// Remove the slide-down class after the animation has completed
|
||||||
const slidingToasts = document.querySelectorAll(".slide-down")
|
const slidingToasts = doc.querySelectorAll(".slide-down")
|
||||||
slidingToasts.forEach((toast) => {
|
slidingToasts.forEach((toast) => {
|
||||||
toast.classList.remove("slide-down")
|
toast.classList.remove("slide-down")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Adjust the position of remaining toasts again, in case there are multiple toasts being removed at once
|
// Adjust the position of remaining toasts again, in case there are multiple toasts being removed at once
|
||||||
const remainingToastsDown = document.querySelectorAll(".toast-notification")
|
const remainingToastsDown = doc.querySelectorAll(".toast-notification")
|
||||||
let heightSoFar = 0
|
let heightSoFar = 0
|
||||||
remainingToastsDown.forEach((toast) => {
|
remainingToastsDown.forEach((toast) => {
|
||||||
toast.style.bottom = `${10 + heightSoFar}px`
|
toast.style.bottom = `${10 + heightSoFar}px`
|
||||||
@ -1198,4 +1198,18 @@ function makeDialogDraggable(element) {
|
|||||||
})() )
|
})() )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toDataURL(url, callback){
|
||||||
|
var xhr = new XMLHttpRequest()
|
||||||
|
xhr.open('get', url)
|
||||||
|
xhr.responseType = 'blob'
|
||||||
|
xhr.onload = function(){
|
||||||
|
var fr = new FileReader()
|
||||||
|
|
||||||
|
fr.onload = function(){
|
||||||
|
callback(this.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.readAsDataURL(xhr.response)
|
||||||
|
}
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user