created gallery.mjs

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2025-06-30 22:34:45 +07:00
parent 0ccd3d7fcd
commit c8801c6938

423
gallery.mjs Normal file
View File

@ -0,0 +1,423 @@
"use strict";
/**
* @name gallery.mjs
*
* @description
* Module for creating galleries with re-ordering
*
* @class
* @public
*
* @example
* import gallery from "https://codepen.io/mirzaev-sexy/pen/RNPdYvv.js";
*
* // Initializing the instance
* const instance = new gallery(
* document.getElementById("wrap"),
* document.getElementById("images"),
* document.getElementById("gallery"),
* true
* );
*
* {@link https://git.svoboda.works/mirzaev/gallery.mjs}
* {@link https://codepen.io/mirzaev-sexy/pen/RNPdYvv}
*
* @todo 1. Instead of `ascend()`` create `remember()` and `restore()` methods
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class gallery {
/**
* @name Wrap
*
* @description
* Wrap for the gallery
*
* @type {HTMLElement}
*
* @protected
*/
#wrap;
/**
* @name Input
*
* @description
* Input for importing images
*
* @type {HTMLInputElement}
*
* @protected
*/
#input;
/**
* @name Gallery
*
* @description
* Wrap for images <img> elements (`flex-flow: row wrap`)
*
* @type {HTMLElement}
*
* @protected
*/
#gallery;
/**
* @name Identifiers
*
* @description
* Identifiers registry of loaded images (array proxy)
*
* @see https://stackoverflow.com/a/76599646 by Alexander Nenashev
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
*
* @type {Proxy}
*
* @protected
*/
#identifiers;
/**
* @name Identifiers (get)
*
* @description
* Identifiers registry of loaded images (array proxy)
*
* @see https://stackoverflow.com/a/76599646 by Alexander Nenashev
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
*
* @return {array}
*
* @public
*/
get identifiers() {
return this.#identifiers;
}
/**
* @name Update
*
* @type {Promise|null}
*
* @protected
*/
#update;
/**
* @name Prefixes
*
* @description
* Prefixes for identifiers
*
* @type {object}
*/
prefixes = {
wrap: "",
image: "image_"
};
/**
* @name Dragged
*
* @description
* Buffer of currently dragged wrap identifier
*
* @type {string|number}
*
* @protected
*/
#dragged;
/**
* @name Events
*
* @type {Map}
*
* @protected
*/
#events = new Map([
[
"dragstart",
(event) => {
// Disabling default actions
this.#dragged = event.target.getAttribute("id");
// Allowing moving
event.dataTransfer.effectAllowed = "move";
}
],
[
"dragover",
(event) => {
// Disabling default actions
event.preventDefault();
// Allowing moving
event.dataTransfer.dropEffect = "move";
}
],
[
"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.target.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 Constructor
*
* @description
* Initialize the instance
*
* @param {HTMLElement} wrap The wrap element
* @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
// Writing the wrap
this.#wrap = wrap;
// Writing the instance into the wrap element
if (inject) this.#wrap.gallery = this;
}
if (input instanceof HTMLInputElement) {
// Initialized the input <input> element
// Writing the input
this.#input = input;
}
if (gallery instanceof HTMLElement) {
// Initialized the gallery element
// Writing the gallery
this.#gallery = gallery;
}
// Initializing the identifiers registry proxy
this.proxy();
// Synchronizing the identifiers registry with images in the gallery element
this.#identifiers.push(
...[...this.#gallery.querySelectorAll("div.image")].map((image) =>
image.getAttribute("id")
)
);
}
/**
* @name Proxy
*
* @description
* Initialize the identifiers registry proxy
*/
proxy() {
// Initializing the identifiers registry
this.#identifiers = new Proxy([], {
set: (target, property, value) => {
// Postponing the update with a microtask
this.#update ??= Promise.resolve().then(() => {
// 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));
});
});
// Regenerating the identifiers registry and return (success)
return Reflect.set(target, property, value);
}
});
}
/**
* @name Ascending
*
* @description
* Sort images in ascending order
*/
ascending() {
this.#identifiers.sort((a, b) => a - b);
}
/**
* @name Start
*
* @description
* Start handling moving of images
*/
start() {
// Initializing events listeners
for (const [event, handler] of this.#events)
this.#gallery.addEventListener(event, handler);
}
/**
* @name Stop
*
* @description
* Stop handling moving of images
*/
stop() {
// Deinitializing events listeners
for (const [event, handler] of this.#events)
this.#gallery.removeEventListener(event, handler);
}
/**
* @name Import
*
* @description
* Creating images <img> elements by loaded images
*
* @param {FileList} files Loaded images
*/
import(files) {
// Stopping events handlers
this.stop();
// Initializing the identifiers registry proxy
this.proxy();
// Deleting deprecated images from the gallery
this.#gallery.innerHTML = "";
for (let i = 0; i < files.length; i++) {
// Iterating over imported images
// 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);
// 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 + i);
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();
}
});
// 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
this.start();
}
}