mega ebanul

This commit is contained in:
2025-07-09 22:51:28 +07:00
parent 2dd5f170b5
commit 13cc5980ee

View File

@@ -10,7 +10,7 @@
* @public
*
* @example
* import gallery from "https://codepen.io/mirzaev-sexy/pen/RNPdYvv.js";
* import gallery from "https://git.svoboda.works/mirzaev/gallery.mjs/raw/branch/stable/gallery.mjs";
*
* // Initializing the instance
* const instance = new gallery(
@@ -41,6 +41,20 @@ export default class gallery {
*/
#wrap;
/**
* @name Wrap (get)
*
* @description
* Wrap for the gallery
*
* @return {HTMLElement}
*
* @public
*/
get wrap() {
return this.#wrap;
}
/**
* @name Input
*
@@ -53,6 +67,20 @@ export default class gallery {
*/
#input;
/**
* @name Input (get)
*
* @description
* Input for importing images
*
* @return {HTMLInputElement}
*
* @public
*/
get input() {
return this.#input;
}
/**
* @name Gallery
*
@@ -65,6 +93,20 @@ export default class gallery {
*/
#gallery;
/**
* @name Gallery (get)
*
* @description
* Wrap for images <img> elements (`flex-flow: row wrap`)
*
* @return {HTMLElement}
*
* @public
*/
get gallery() {
return this.#gallery;
}
/**
* @name Identifiers
*
@@ -119,6 +161,16 @@ export default class gallery {
image: "image_"
};
/**
* @name Allowed
*
* @description
* Regular expression for checking matching to images extensions (not MIME-types)
*
* @type {RegExp}
*/
allowed = /\.(jpe?g|png|gif|webp)$/i;
/**
* @name Dragged
*
@@ -131,6 +183,20 @@ export default class gallery {
*/
#dragged;
/**
* @name Dragged (get)
*
* @description
* Buffer of currently dragged wrap identifier
*
* @type {string|number}
*
* @public
*/
get dragged() {
return this.#dragged;
}
/**
* @name Events
*
@@ -139,6 +205,9 @@ export default class gallery {
* @protected
*/
#events = new Map([
[
"system",
new Map([
[
"dragstart",
(event) => {
@@ -147,6 +216,9 @@ export default class gallery {
// Allowing moving
event.dataTransfer.effectAllowed = "move";
// Processing the modification event
this.#events.get("moving").get("dragstart")(event);
}
],
@@ -158,6 +230,9 @@ export default class gallery {
// Allowing moving
event.dataTransfer.dropEffect = "move";
// Processing the modification event
this.#events.get("moving").get("dragover")(event);
}
],
@@ -173,6 +248,9 @@ export default class gallery {
// Writing class about targeting
wrap?.classList.add("target");
}
// Processing the modification event
this.#events.get("moving").get("dragenter")(event);
}
],
@@ -188,6 +266,9 @@ export default class gallery {
// Deleting class about targeting
wrap.classList.remove("target");
}
// Processing the modification event
this.#events.get("moving").get("dragleave")(event);
}
],
@@ -203,6 +284,9 @@ export default class gallery {
// Deleting class about targeting
wrap.classList.remove("target");
}
// Processing the modification event
this.#events.get("moving").get("dragend")(event);
}
],
@@ -234,10 +318,45 @@ export default class gallery {
this.#identifiers[from]
];
}
// Processing the modification event
this.#events.get("moving").get("drop")(event);
}
]
])
],
[
"moving",
new Map([
["dragstart", (event) => {}],
["dragover", (event) => {}],
["dragenter", (event) => {}],
["dragleave", (event) => {}],
["dragend", (event) => {}],
["drop", (event) => {}]
])
],
["wrap", new Map([["delete", (event) => {}]])]
]);
/**
* @name Events (get)
*
* @type {Map}
*
* @public
*/
get events() {
return this.#events;
}
/**
* @name Constructor
*
@@ -248,7 +367,7 @@ export default class gallery {
* @param {HTMLInputElement} input The input <input> element
* @param {HTMLElement} gallery The gallery element
* @param {boolean} [inject=false] Write the instance into the wrap element?
**/
*/
constructor(wrap, input, gallery, inject = false) {
if (wrap instanceof HTMLElement) {
// Initialized the wrap element
@@ -292,7 +411,7 @@ export default class gallery {
* Initialize the identifiers registry proxy
*/
proxy() {
// Initializing the identifiers registry
// Initializing the identifiers registry proxy
this.#identifiers = new Proxy([], {
set: (target, property, value) => {
// Postponing the update with a microtask
@@ -300,14 +419,10 @@ export default class gallery {
// Deinitializing the update promise
this.#update = null;
// Generating the order row (can be deleted without any problems)
document.getElementById("order").textContent =
"Order: " + target.join(", ");
// Re-ordering wraps <div> elements by the identifiers registry
target.forEach((identifier) => {
this.#gallery.appendChild(document.getElementById(identifier));
});
target.forEach((identifier) =>
this.#gallery.appendChild(document.getElementById(identifier))
);
});
// Regenerating the identifiers registry and return (success)
@@ -323,6 +438,7 @@ export default class gallery {
* Sort images in ascending order
*/
ascending() {
// Sorting
this.#identifiers.sort((a, b) => a - b);
}
@@ -334,9 +450,10 @@ export default class gallery {
*/
start() {
// Initializing events listeners
for (const [event, handler] of this.#events)
for (const [event, handler] of this.#events.get("system")) {
this.#gallery.addEventListener(event, handler);
}
}
/**
* @name Stop
@@ -346,9 +463,75 @@ export default class gallery {
*/
stop() {
// Deinitializing events listeners
for (const [event, handler] of this.#events)
for (const [event, handler] of this.#events.get("system")) {
this.#gallery.removeEventListener(event, handler);
}
}
/**
* @name Generate
*
* @description
* Create the wrap with images and buttons
*
* @param {number|string} order Number for generating identifiers
* @param {(File|string)} target The image for `srt` attribute
*
* @returns {HTMLElement} Created wrap <div> element with images and buttons
*/
generate(order, target) {
// Creating the wrap <div> element
const wrap = document.createElement("div");
wrap.classList.add("image");
wrap.setAttribute("id", this.prefixes.wrap + order);
wrap.setAttribute("draggable", true);
// Creating the button <button> element
const button = document.createElement("button");
button.classList.add("delete");
button.addEventListener("click", (event) => {
// Deleting the identifier
// Initializing index of the wrap
const index = this.#identifiers.indexOf(this.prefixes.wrap + order);
if (index > -1) {
// Initialized index of the wrap
// Deleting identifier of the wrap from the identifiers registry
this.#identifiers.splice(index, 1);
// Deleting the wrap
wrap.remove();
// Processing the `ondelete` function
this.#events.get("wrap")?.get("delete")(event);
}
});
// Creating the trash icon <i> element
const trash = document.createElement("i");
trash.classList.add("icon", "trash");
// Creating the image <img> element
const image = document.createElement("img");
image.setAttribute("id", this.prefixes.image + order);
image.setAttribute("draggable", false);
image.setAttribute(
"src",
target instanceof File
? window.URL.createObjectURL(target)
: target + "?updated=" + Date.now()
);
// Assembling
wrap.appendChild(image);
button.appendChild(trash);
wrap.appendChild(button);
// Exit (success)
return wrap;
}
/**
* @name Import
@@ -356,7 +539,7 @@ export default class gallery {
* @description
* Creating images <img> elements by loaded images
*
* @param {FileList} files Loaded images
* @param {(FileList|object} files Files for importing (can be array of URL`s)
*/
import(files) {
// Stopping events handlers
@@ -368,56 +551,137 @@ export default class gallery {
// Deleting deprecated images from the gallery
this.#gallery.innerHTML = "";
for (let i = 0; i < files.length; i++) {
// Iterating over imported images
for (const [index, file] of files instanceof FileList
? Object.entries(files)
: files) {
// Iterating over files
// Creating the wrap <div> element
const wrap = document.createElement("div");
wrap.classList.add("image");
wrap.setAttribute("id", this.prefixes.wrap + i);
wrap.setAttribute("draggable", true);
if (file) {
// Initialized the file
// Creating the button <button> element
const button = document.createElement("button");
button.classList.add("delete");
button.addEventListener("click", (event) => {
// Deleting the identifier
// Initializing the file extension
const extension = file.name ?? file.match(/\.\w{3,4}$/)[0];
// Initializing index of the wrap
const index = this.#identifiers.indexOf(this.prefixes.wrap + i);
if (this.allowed.test(extension)) {
// Allowed the file
if (index > -1) {
// Initialized index of the wrap
// Generating HTML elements
const wrap = this.generate(index, file);
// Deleting identifier of the wrap from the identifiers registry
this.#identifiers.splice(index, 1);
// Deleting the wrap
wrap.remove();
}
});
// Creating the trash icon <i> element
const trash = document.createElement("i");
trash.classList.add("icon", "trash");
// Creating the image <img> element
const image = document.createElement("img");
image.setAttribute("id", this.prefixes.image + i);
image.setAttribute("draggable", false);
image.setAttribute("src", URL.createObjectURL(files[i]));
// Writing into the gallery
button.appendChild(trash);
wrap.appendChild(image);
wrap.appendChild(button);
// Injecting HTML elements into the document
this.#gallery.appendChild(wrap);
// Writing into the identifiers registry
this.#identifiers.push(wrap.getAttribute("id"));
}
}
}
// Starting events handlers
this.start();
}
/**
* @name Export
*
* @description
* Collect images and convert to Blob object or Base64 string
*
* @param {(string|number)} [identifier] Identifier of the wrap
* @param {boolean} [base64=false] Convert to Base64 string instead of Blob object
*
* @returns {Promise}
*/
async export(identifier, base64 = false) {
// Initializing the reader
const reader = new FileReader();
if (typeof identifier === "string" || typeof identifier === "number") {
// Requested specified image
// Initializing the image
const image = this.#gallery.querySelector(
"div.image#" + CSS.escape(identifier) + ">img"
);
if (image instanceof HTMLImageElement) {
// Initialized the image <img> element
// Initializing the image content
const content = image.getAttribute("src")?.split("?")[0];
try {
if (new URL(content).protocol === "blob:") {
// Blob
// Exit (success/fail)
return new Promise((resolve) => {
// Initializing listener for the "LoadEnd" event
reader.onloadend = () => resolve(reader.result);
fetch(content)
.then((r) => r.blob())
.then((value) => {
// Converted "blob:..." string to Blob object
if (base64) {
// Base64 string
// Converting blob to base64
reader.readAsDataURL(value);
} else {
// Blob object
// Exit (success)
resolve(value);
}
});
});
} else {
// Base64 or HTTP
// Exit (success)
return content;
}
} catch {
// Base64 or HTTP
// Exit (success)
return content;
}
}
} else {
// Requested all images
// Initializing wraps
const wraps = this.#gallery.querySelectorAll("div.image");
if (wraps.length > 0) {
// Initialized wraps
// Initialize the converted images buffer
const converted = [];
for (const wrap of wraps) {
// Iterating over images
// Initializing the wrap identifier
const identifier = wrap.getAttribute("id");
if (
typeof identifier === "string" ||
typeof identifier === "number"
) {
// Initialized the wrap identifier
// Converting the image and writing into the converted images buffer
converted.push((await this.export(identifier)) ?? null);
}
}
// Exit (success)
return converted;
}
}
}
}