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 * @public
* *
* @example * @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 * // Initializing the instance
* const instance = new gallery( * const instance = new gallery(
@@ -41,6 +41,20 @@ export default class gallery {
*/ */
#wrap; #wrap;
/**
* @name Wrap (get)
*
* @description
* Wrap for the gallery
*
* @return {HTMLElement}
*
* @public
*/
get wrap() {
return this.#wrap;
}
/** /**
* @name Input * @name Input
* *
@@ -53,6 +67,20 @@ export default class gallery {
*/ */
#input; #input;
/**
* @name Input (get)
*
* @description
* Input for importing images
*
* @return {HTMLInputElement}
*
* @public
*/
get input() {
return this.#input;
}
/** /**
* @name Gallery * @name Gallery
* *
@@ -65,6 +93,20 @@ export default class gallery {
*/ */
#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 * @name Identifiers
* *
@@ -119,6 +161,16 @@ export default class gallery {
image: "image_" 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 * @name Dragged
* *
@@ -131,6 +183,20 @@ export default class gallery {
*/ */
#dragged; #dragged;
/**
* @name Dragged (get)
*
* @description
* Buffer of currently dragged wrap identifier
*
* @type {string|number}
*
* @public
*/
get dragged() {
return this.#dragged;
}
/** /**
* @name Events * @name Events
* *
@@ -140,104 +206,157 @@ export default class gallery {
*/ */
#events = new Map([ #events = new Map([
[ [
"dragstart", "system",
(event) => { new Map([
// Disabling default actions [
this.#dragged = event.target.getAttribute("id"); "dragstart",
(event) => {
// Disabling default actions
this.#dragged = event.target.getAttribute("id");
// Allowing moving // Allowing moving
event.dataTransfer.effectAllowed = "move"; event.dataTransfer.effectAllowed = "move";
}
// Processing the modification event
this.#events.get("moving").get("dragstart")(event);
}
],
[
"dragover",
(event) => {
// Disabling default actions
event.preventDefault();
// Allowing moving
event.dataTransfer.dropEffect = "move";
// Processing the modification event
this.#events.get("moving").get("dragover")(event);
}
],
[
"dragenter",
(event) => {
// Searching for the wrap
const wrap = event.target.closest("div.image");
if (wrap?.getAttribute("id") !== this.#dragged) {
// Wrap is not currently draggable wrap
// Writing class about targeting
wrap?.classList.add("target");
}
// Processing the modification event
this.#events.get("moving").get("dragenter")(event);
}
],
[
"dragleave",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (wrap instanceof HTMLElement) {
// Found the wrap
// Deleting class about targeting
wrap.classList.remove("target");
}
// Processing the modification event
this.#events.get("moving").get("dragleave")(event);
}
],
[
"dragend",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (wrap instanceof HTMLElement) {
// Found the wrap
// Deleting class about targeting
wrap.classList.remove("target");
}
// Processing the modification event
this.#events.get("moving").get("dragend")(event);
}
],
[
"drop",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (
wrap instanceof HTMLElement &&
wrap.getAttribute("id") &&
wrap.getAttribute("id") !== this.#dragged
) {
// Found the wrap and has it identifier and it not currently draggable wrap
// Deleting class about targeting from every wrap
this.#gallery
.querySelector("div.image.target")
?.classList.remove("target");
// Initializing indexes of wraps in the identifiers registry
const from = this.#identifiers.indexOf(this.#dragged);
const to = this.#identifiers.indexOf(wrap.getAttribute("id"));
// Swapping wraps
[this.#identifiers[from], this.#identifiers[to]] = [
this.#identifiers[to],
this.#identifiers[from]
];
}
// Processing the modification event
this.#events.get("moving").get("drop")(event);
}
]
])
], ],
[ [
"dragover", "moving",
(event) => { new Map([
// Disabling default actions ["dragstart", (event) => {}],
event.preventDefault();
// Allowing moving ["dragover", (event) => {}],
event.dataTransfer.dropEffect = "move";
} ["dragenter", (event) => {}],
["dragleave", (event) => {}],
["dragend", (event) => {}],
["drop", (event) => {}]
])
], ],
[ ["wrap", new Map([["delete", (event) => {}]])]
"dragenter",
(event) => {
// Searching for the wrap
const wrap = event.target.closest("div.image");
if (wrap?.getAttribute("id") !== this.#dragged) {
// Wrap is not currently draggable wrap
// Writing class about targeting
wrap?.classList.add("target");
}
}
],
[
"dragleave",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (wrap instanceof HTMLElement) {
// Found the wrap
// Deleting class about targeting
wrap.classList.remove("target");
}
}
],
[
"dragend",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (wrap instanceof HTMLElement) {
// Found the wrap
// Deleting class about targeting
wrap.classList.remove("target");
}
}
],
[
"drop",
(event) => {
// Searching for the closest parent wrap
const wrap = event.target.closest("div.image");
if (
wrap instanceof HTMLElement &&
wrap.getAttribute("id") &&
wrap.getAttribute("id") !== this.#dragged
) {
// Found the wrap and has it identifier and it not currently draggable wrap
// Deleting class about targeting from every wrap
this.#gallery
.querySelector("div.image.target")
?.classList.remove("target");
// Initializing indexes of wraps in the identifiers registry
const from = this.#identifiers.indexOf(this.#dragged);
const to = this.#identifiers.indexOf(wrap.getAttribute("id"));
// Swapping wraps
[this.#identifiers[from], this.#identifiers[to]] = [
this.#identifiers[to],
this.#identifiers[from]
];
}
}
]
]); ]);
/**
* @name Events (get)
*
* @type {Map}
*
* @public
*/
get events() {
return this.#events;
}
/** /**
* @name Constructor * @name Constructor
* *
@@ -248,7 +367,7 @@ export default class gallery {
* @param {HTMLInputElement} input The input <input> element * @param {HTMLInputElement} input The input <input> element
* @param {HTMLElement} gallery The gallery element * @param {HTMLElement} gallery The gallery element
* @param {boolean} [inject=false] Write the instance into the wrap element? * @param {boolean} [inject=false] Write the instance into the wrap element?
**/ */
constructor(wrap, input, gallery, inject = false) { constructor(wrap, input, gallery, inject = false) {
if (wrap instanceof HTMLElement) { if (wrap instanceof HTMLElement) {
// Initialized the wrap element // Initialized the wrap element
@@ -292,7 +411,7 @@ export default class gallery {
* Initialize the identifiers registry proxy * Initialize the identifiers registry proxy
*/ */
proxy() { proxy() {
// Initializing the identifiers registry // Initializing the identifiers registry proxy
this.#identifiers = new Proxy([], { this.#identifiers = new Proxy([], {
set: (target, property, value) => { set: (target, property, value) => {
// Postponing the update with a microtask // Postponing the update with a microtask
@@ -300,14 +419,10 @@ export default class gallery {
// Deinitializing the update promise // Deinitializing the update promise
this.#update = null; 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 // Re-ordering wraps <div> elements by the identifiers registry
target.forEach((identifier) => { target.forEach((identifier) =>
this.#gallery.appendChild(document.getElementById(identifier)); this.#gallery.appendChild(document.getElementById(identifier))
}); );
}); });
// Regenerating the identifiers registry and return (success) // Regenerating the identifiers registry and return (success)
@@ -323,6 +438,7 @@ export default class gallery {
* Sort images in ascending order * Sort images in ascending order
*/ */
ascending() { ascending() {
// Sorting
this.#identifiers.sort((a, b) => a - b); this.#identifiers.sort((a, b) => a - b);
} }
@@ -334,8 +450,9 @@ export default class gallery {
*/ */
start() { start() {
// Initializing events listeners // Initializing events listeners
for (const [event, handler] of this.#events) for (const [event, handler] of this.#events.get("system")) {
this.#gallery.addEventListener(event, handler); this.#gallery.addEventListener(event, handler);
}
} }
/** /**
@@ -346,8 +463,74 @@ export default class gallery {
*/ */
stop() { stop() {
// Deinitializing events listeners // Deinitializing events listeners
for (const [event, handler] of this.#events) for (const [event, handler] of this.#events.get("system")) {
this.#gallery.removeEventListener(event, handler); 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;
} }
/** /**
@@ -356,7 +539,7 @@ export default class gallery {
* @description * @description
* Creating images <img> elements by loaded images * 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) { import(files) {
// Stopping events handlers // Stopping events handlers
@@ -368,56 +551,137 @@ export default class gallery {
// Deleting deprecated images from the gallery // Deleting deprecated images from the gallery
this.#gallery.innerHTML = ""; this.#gallery.innerHTML = "";
for (let i = 0; i < files.length; i++) { for (const [index, file] of files instanceof FileList
// Iterating over imported images ? Object.entries(files)
: files) {
// Iterating over files
// Creating the wrap <div> element if (file) {
const wrap = document.createElement("div"); // Initialized the file
wrap.classList.add("image");
wrap.setAttribute("id", this.prefixes.wrap + i);
wrap.setAttribute("draggable", true);
// Creating the button <button> element // Initializing the file extension
const button = document.createElement("button"); const extension = file.name ?? file.match(/\.\w{3,4}$/)[0];
button.classList.add("delete");
button.addEventListener("click", (event) => {
// Deleting the identifier
// Initializing index of the wrap if (this.allowed.test(extension)) {
const index = this.#identifiers.indexOf(this.prefixes.wrap + i); // Allowed the file
if (index > -1) { // Generating HTML elements
// Initialized index of the wrap const wrap = this.generate(index, file);
// Deleting identifier of the wrap from the identifiers registry // Injecting HTML elements into the document
this.#identifiers.splice(index, 1); this.#gallery.appendChild(wrap);
// Deleting the wrap // Writing into the identifiers registry
wrap.remove(); this.#identifiers.push(wrap.getAttribute("id"));
} }
}); }
// 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);
this.#gallery.appendChild(wrap);
// Writing into the identifiers registry
this.#identifiers.push(wrap.getAttribute("id"));
} }
// Starting events handlers // Starting events handlers
this.start(); 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;
}
}
}
} }