From c94542bfd3c843d06f3a24611c07da12cc5ae14e Mon Sep 17 00:00:00 2001 From: mirzaev Date: Sun, 3 Aug 2025 20:07:43 +0700 Subject: [PATCH] layer class, events, injected parameters --- pechatalka.min.mjs | 2 +- pechatalka.mjs | 1047 +++++++++++++++++++++++++++++++------------- 2 files changed, 747 insertions(+), 302 deletions(-) diff --git a/pechatalka.min.mjs b/pechatalka.min.mjs index 0f1ef7e..dfcc9d2 100644 --- a/pechatalka.min.mjs +++ b/pechatalka.min.mjs @@ -1 +1 @@ -"use strict";export default class pechatalka{#wrap=document.getElementById("pechatalka");get wrap(){return this.#wrap}#canvas=document.getElementById("pechatalka")?.querySelector(".canvas");get canvas(){return this.#canvas}#result=document.getElementById("pechatalka")?.querySelector(".result");get result(){return this.#result}#layers=new Set();get layers(){return this.#layers}#cost=0;get cost(){return this.#cost}prices={pin:{image:150}};constructor(wrap,canvas,result,inject=false){if(wrap instanceof HTMLElement){this.#wrap=wrap;if(inject){this.#wrap.pechatalka=this}}if(canvas instanceof HTMLElement){this.#canvas=canvas}if(result instanceof HTMLElement){this.#result=result}}moving(target){const canvas=this.#canvas;const from={x:0,y:0};function moving(event){target.style.left=event.clientX-from.x+"px";target.style.top=event.clientY-from.y+"px"}function restore(){target.style.top=target.style.left=null}function start(event){if(event.button===0){[from.x,from.y]=[event.clientX-(parseInt(target.style.left)||0),event.clientY-(parseInt(target.style.top)||0)];window.addEventListener("mousemove",moving,true)}}function end(){window.removeEventListener("mousemove",moving,true)}target.addEventListener("mousedown",start,false);window.addEventListener("mouseup",end,false);canvas.addEventListener("mouseleave",end,false)}scaling(target,type="scale"){function scroll(event){if(type==="scale"){let scale=(parseFloat(target.style.scale)||1)+event.deltaY/1000;if(scale<0.4){scale=0.4}else if(scale>3){scale=3}target.style.scale=scale}else if(type==="width"){const change=event.deltaY/2;const cut=target.parentElement.offsetWidth;const bounds={minimum:cut/2-cut,maximum:cut*2-cut};let zoom=(parseFloat(target.style.getPropertyValue("--width-zoom"))||0)+change;if(zoombounds.maximum){zoom=bounds.maximum}else{target.style.left=(parseInt(target.style.left)||0)-change/2+"px";target.style.top=(parseInt(target.style.top)||0)-change/2+"px"}target.style.setProperty("--width-zoom",zoom+"px")}}target.addEventListener("wheel",scroll,false)}image(file){const identifier=this.#layers.entries.length??1;const layer=document.createElement("div");layer.classList.add("layer");layer.setAttribute("id","pechatalka_layer_"+identifier);const button_delete=document.createElement("button");button_delete.classList.add("delete","rounded");button_delete.addEventListener("click",(event)=>{layer.remove();this.#cost-=this.prices.pin.image??0;this.#result.querySelector(".cost").innerText=this.#cost});const trash=document.createElement("i");trash.classList.add("icon","trash");const image=document.createElement("img");image.classList.add("rounded");image.setAttribute("draggable",false);image.setAttribute("src",URL.createObjectURL(file));layer.appendChild(image);button_delete.appendChild(trash);layer.appendChild(button_delete);this.#canvas.appendChild(layer);this.#cost+=this.prices.pin.image??0;this.#result.querySelector(".cost").innerText=this.#cost;this.moving(layer);this.scaling(layer)}} \ No newline at end of file +"use strict";export default class pechatalka{#wrap=document.getElementById("pechatalka");get wrap(){return this.#wrap}#canvas=document.getElementById("pechatalka")?.querySelector(".canvas");get canvas(){return this.#canvas}#result=document.getElementById("pechatalka")?.querySelector(".result");get result(){return this.#result}#layers=new Set();get layers(){return this.#layers}#preset=new Map();get preset(){return this.#preset}#cost=0;set cost(value){if(typeof value==="number"){const from=this.#cost;this.#cost=value;if(this.#cost<0){this.#cost=0}this.#events.get("cost")?.get("changed")(this.#cost,from)}}get cost(){return this.#cost}prices={pin:{image:150}};#events=new Map([["layers",new Map([["create",(layer)=>{}]])],["cost",new Map([["changed",(to,from)=>{}]])]]);get events(){return this.#events}constructor(wrap,canvas,result,preset,inject=false){if(wrap instanceof HTMLElement){this.#wrap=wrap;if(inject){this.#wrap.pechatalka=this}}if(canvas instanceof HTMLElement){this.#canvas=canvas}if(result instanceof HTMLElement){this.#result=result}if(preset instanceof Map){this.#preset=preset}}global(name,value=null,preset=false){if(typeof name==="string"){for(const layer of this.#layers){layer.set(name,value)}if(preset){this.#preset.set(name,value)}}}moving(layer){const from={x:0,y:0};function moving(event){layer.wrap.style.left=event.clientX-from.x+"px";layer.wrap.style.top=event.clientY-from.y+"px"}function restore(){layer.wrap.style.top=layer.wrap.style.left=null}function start(event){if(event.button===0){[from.x,from.y]=[event.clientX-(parseInt(layer.wrap.style.left)||0),event.clientY-(parseInt(layer.wrap.style.top)||0)];window.addEventListener("mousemove",moving,true)}}function end(){window.removeEventListener("mousemove",moving,true)}layer.wrap.addEventListener("mousedown",start,false);window.addEventListener("mouseup",end,false);this.#canvas.addEventListener("mouseleave",end,false)}scaling(layer,type="scale"){function scroll(event){if(type==="scale"){let scale=(parseFloat(layer.wrap.style.scale)||1)+event.deltaY/1000;if(scale<0.4){scale=0.4}else if(scale>3){scale=3}layer.wrap.style.scale=scale}else if(type==="width"){const change=event.deltaY/1.5;const cut=target.parentElement.offsetWidth;const bounds={minimum:cut/1.5-cut,maximum:cut*1.5-cut};let zoom=(parseFloat(layer.wrap.style.getPropertyValue("--width-zoom"))||0)+change;if(zoombounds.maximum){zoom=bounds.maximum}else{layer.wrap.style.left=(parseInt(layer.wrap.style.left)||0)-change/2+"px";layer.wrap.style.top=(parseInt(layer.wrap.style.top)||0)-change/2+"px"}layer.wrap.style.setProperty("--width-zoom",zoom+"px")}}layer.wrap.addEventListener("wheel",scroll,false)}image(file,cost=0){const identifier=this.#layers.size+1;const wrap=document.createElement("div");wrap.classList.add("layer");wrap.setAttribute("id","pechatalka_layer_"+identifier);const button_delete=document.createElement("button");button_delete.classList.add("delete");const trash=document.createElement("i");trash.classList.add("icon","trash");const image=document.createElement("img");image.setAttribute("draggable",false);image.setAttribute("src",URL.createObjectURL(file));wrap.appendChild(image);button_delete.appendChild(trash);wrap.appendChild(button_delete);this.#canvas.appendChild(wrap);const instance=new layer("image",cost,wrap,image,{delete:button_delete},this.#preset,);this.#layers.add(instance);this.#events.get("layers")?.get("create")(instance);this.cost+=instance.cost;this.moving(instance);this.scaling(instance);button_delete.addEventListener("click",(event)=>{instance.wrap.remove();this.#layers.delete(instance);this.cost-=instance.cost})}}export class layer{#type;set type(value){const types=new Set(["image","film"]);if(types.has(value)){this.#type=value}}get type(){return this.#type}#cost=0;set cost(value){if(typeof value==="number"){const from=this.#cost;this.#cost=value;if(this.#cost<0){this.#cost=0}this.#events.get("cost")?.get("changed")(from,this.#cost)}}get cost(){return this.#cost}#wrap;get wrap(){return this.#wrap}#content;get content(){return this.#content}#buttons=new Map();get buttons(){return this.#buttons}#events=new Map([["cost",new Map([["changed",(to,from)=>{}]])]]);get events(){return this.#events}constructor(type,cost,wrap,content,buttons,preset,inject=false){this.type=type;if(typeof this.#type==="string"){this.cost=cost;if(wrap instanceof HTMLElement){this.#wrap=wrap;if(inject){this.#wrap.layer=this}}if(content instanceof HTMLElement){this.#content=content}if(buttons instanceof Object){for(const[name,element]of Object.entries(buttons)){this.#buttons.set(name,element)}}for(const[name,value]of preset.entries()){this[name]=value}}}set(name,value){const from=this[name];this[name]=value;this.#events.get(name)?.get("set")(this[name],from);return this[name]}toggle(name){this[name]=!this[name]??true;this.#events.get(name)?.get("toggle")(this[name]);return this[name]}} diff --git a/pechatalka.mjs b/pechatalka.mjs index c2365e7..6a4ac65 100644 --- a/pechatalka.mjs +++ b/pechatalka.mjs @@ -27,357 +27,802 @@ * @author Arsen Mirzaev Tatyano-Muradovich */ export default class pechatalka { - /** - * @name Wrap - * - * @type {HTMLElement} - * - * @protected - */ - #wrap = document.getElementById("pechatalka"); + /** + * @name Wrap + * + * @type {HTMLElement} + * + * @protected + */ + #wrap = document.getElementById("pechatalka"); - /** - * @name Wrap (get) - * - * @return {HTMLElement} - * - * @public - */ - get wrap() { - return this.#wrap; - } + /** + * @name Wrap (get) + * + * @return {HTMLElement} + * + * @public + */ + get wrap() { + return this.#wrap; + } - /** - * @name Canvas - * - * @type {HTMLElement} - * - * @protected - */ - #canvas = document.getElementById("pechatalka")?.querySelector(".canvas"); + /** + * @name Canvas + * + * @type {HTMLElement} + * + * @protected + */ + #canvas = document.getElementById("pechatalka")?.querySelector(".canvas"); - /** - * @name Canvas (get) - * - * @return {HTMLElement} - * - * @public - */ - get canvas() { - return this.#canvas; - } + /** + * @name Canvas (get) + * + * @return {HTMLElement} + * + * @public + */ + get canvas() { + return this.#canvas; + } - /** - * @name Result - * - * @type {HTMLElement} - * - * @protected - */ - #result = document.getElementById("pechatalka")?.querySelector(".result"); + /** + * @name Result + * + * @type {HTMLElement} + * + * @protected + */ + #result = document.getElementById("pechatalka")?.querySelector(".result"); - /** - * @name Result (get) - * - * @return {HTMLElement} - * - * @public - */ - get result() { - return this.#result; - } + /** + * @name Result (get) + * + * @return {HTMLElement} + * + * @public + */ + get result() { + return this.#result; + } - /** - * @name Layers - * - * @type {Set} - * - * @protected - */ - #layers = new Set(); + /** + * @name Layers + * + * @type {Set} + * + * @protected + */ + #layers = new Set(); - /** - * @name Layers (get) - * - * @return {Set} - * - * @public - */ - get layers() { - return this.#layers; - } + /** + * @name Layers (get) + * + * @return {Set} + * + * @public + */ + get layers() { + return this.#layers; + } - /** - * @name Cost - * - * @type {number} - * - * @protected - */ - #cost = 0; + /** + * @name Preset + * + * @description + * Registry of parameters that will be write into created layers + * + * @type {Map} + * + * @protected + */ + #preset = new Map(); - /** - * @name Cost (get) - * - * @return {number} - * - * @public - */ - get cost() { - return this.#cost; - } + /** + * @name Preset (get) + * + * @description + * Registry of parameters that will be write into created layers + * + * @return {Map} + * + * @public + */ + get preset() { + return this.#preset; + } - /** - * @name Prices - * - * @description - * Prices for calculating the total cost - * - * @return {object} - * - * @public - */ - prices = { - pin: { - image: 150 - } - }; + /** + * @name Cost + * + * @description + * The total cost + * + * @type {number} + * + * @protected + */ + #cost = 0; - /** - * @name Constructor - * - * @description - * Initialize the instance - * - * @param {HTMLElement} wrap The wrap element - * @param {HTMLElement} canvas The canvas element - * @param {HTMLElement} result The result element - * @param {boolean} [inject=false] Write the instance into the wrap element? - */ - constructor(wrap, canvas, result, inject = false) { - if (wrap instanceof HTMLElement) { - // Initialized the wrap element + /** + * @name Cost (set) + * + * @description + * The total cost + * + * @return {number} + * + * @public + */ + set cost(value) { + if (typeof value === "number") { + // Validated the value - // Writing the wrap - this.#wrap = wrap; + // Initializing the deprecated cost + const from = this.#cost; - // Writing the instance into the wrap element - if (inject) this.#wrap.pechatalka = this; - } + // Writing the value + this.#cost = value; - if (canvas instanceof HTMLElement) { - // Initialized the canvas element + // Filtering by the minimal value + if (this.#cost < 0) this.#cost = 0; - // Writing the canvas - this.#canvas = canvas; - } + // Processing the `cost changed` event function + this.#events.get("cost")?.get("changed")(this.#cost, from); + } + } - if (result instanceof HTMLElement) { - // Initialized the result element + /** + * @name Cost (get) + * + * @description + * The total cost + * + * @return {number} + * + * @public + */ + get cost() { + return this.#cost; + } - // Writing the result - this.#result = result; - } - } + /** + * @name Prices + * + * @description + * Prices for calculating the total cost + * + * @return {object} + * + * @public + */ + prices = { + pin: { + image: 150, + }, + }; - /** - * @name Moving - * - * @description - * Add moving for the target - * - * @param {HTMLElement} target - */ - moving(target) { - // Initializing the link to the canvas - const canvas = this.#canvas; + /** + * @name Events + * + * @type {Map} + * + * @protected + */ + #events = new Map([ + ["layers", new Map([["create", (layer) => {}]])], + ["cost", new Map([["changed", (to, from) => {}]])], + ]); - // Initializing the start moving cursor coordinates buffer - const from = { x: 0, y: 0 }; + /** + * @name Events (get) + * + * @type {Map} + * + * @public + */ + get events() { + return this.#events; + } - /** - * @name Moving - */ - function moving(event) { - // Writing the X coordinate - target.style.left = event.clientX - from.x + "px"; + /** + * @name Constructor + * + * @description + * Initialize the instance of Pechatalka + * + * @param {HTMLElement} wrap The wrap element + * @param {HTMLElement} canvas The canvas element + * @param {HTMLElement} result The result element + * @param {(Map|null)} [preset=null] Preset parameters for layers + * @param {boolean} [inject=false] Write the instance into the wrap element? + */ + constructor(wrap, canvas, result, preset, inject = false) { + if (wrap instanceof HTMLElement) { + // Initialized the wrap element - // Writing the Y coordinate - target.style.top = event.clientY - from.y + "px"; - } + // Writing the wrap + this.#wrap = wrap; - /** - * @name Restore - */ - function restore() { - // Restoring initial coordinates - target.style.top = target.style.left = null; - } + // Writing the instance into the wrap element + if (inject) this.#wrap.pechatalka = this; + } - /** - * @name Start - */ - function start(event) { - if (event.button === 0) { - // Pressed the main mouse button (left by default) + if (canvas instanceof HTMLElement) { + // Initialized the canvas element - // Writing the start moving cursor coordinates - [from.x, from.y] = [ - event.clientX - (parseInt(target.style.left) || 0), - event.clientY - (parseInt(target.style.top) || 0) - ]; + // Writing the canvas + this.#canvas = canvas; + } - // Initializing the event listener - window.addEventListener("mousemove", moving, true); - } - } + if (result instanceof HTMLElement) { + // Initialized the result element - /** - * @name End - */ - function end() { - // Initializing the event listener - window.removeEventListener("mousemove", moving, true); - } + // Writing the result + this.#result = result; + } - // Initializing event listeners - target.addEventListener("mousedown", start, false); - window.addEventListener("mouseup", end, false); - canvas.addEventListener("mouseleave", end, false); - } + if (preset instanceof Map) { + // Received the preset registry - /** - * @name Scaling - * - * @description - * Add resizing for the target - * - * 1. Resizing by changing the `scale` parameter disables the buttons visibility - * outside the cut borders (`overflow: fixed` did not work) - * - * 2. Resizing by changing the `width` parameter has problems with boundaries, - * that is it has movement glitches - * - * @param {HTMLElement} target - * @param {string} [type='scale'] Type of scaling (scale, width) - */ - scaling(target, type = "scale") { - /** - * @name Scroll - */ - function scroll(event) { - if (type === "scale") { - // Scaling by changing scale + // Writing the preset registry + this.#preset = preset; + } + } - // Initializing new scale - let scale = (parseFloat(target.style.scale) || 1) + event.deltaY / 1000; + /** + * @name Global + * + * @description + * Write the parameter into all layers + * + * @param {string} name Name of the parameter + * @param {(Object|string|number|boolean|null)} [value=null] Value of the parameter + * @param {boolean} [preset=false] Reinitialize the parameter in the preset registry? + */ + global(name, value = null, preset = false) { + if (typeof name === "string") { + // Received required arguments - // Normalization and protection against out of scale boundaries - if (scale < 0.4) scale = 0.4; - else if (scale > 3) scale = 3; + for (const layer of this.#layers) { + // Iterating over layers - // Writing the scale - target.style.scale = scale; - } else if (type === "width") { - // Scaling by changing width + // Reinitializing the layer parameter + layer.set(name, value); + } - // Initializing the zoom changing value - const change = event.deltaY / 2; + if (preset) { + // Requested to reinitialize the parameter in the preset registry - // Initializing width of the cut space - const cut = target.parentElement.offsetWidth; + // Writing the parameter into the preset registry + this.#preset.set(name, value); + } + } + } - // Initializing bounds for zooming - const bounds = { - minimum: cut / 2 - cut, - maximum: cut * 2 - cut - }; + /** + * @name Moving + * + * @description + * Add moving for the layer + * + * @param {layer} layer + */ + moving(layer) { + // Initializing the start moving cursor coordinates buffer + const from = { x: 0, y: 0 }; - // Initializing new scale - let zoom = - (parseFloat(target.style.getPropertyValue("--width-zoom")) || 0) + - change; + /** + * @name Moving + */ + function moving(event) { + // Writing the X coordinate + layer.wrap.style.left = event.clientX - from.x + "px"; - if (zoom < bounds.minimum) zoom = bounds.minimum; - else if (zoom > bounds.maximum) zoom = bounds.maximum; - else { - // The layer scale was changed + // Writing the Y coordinate + layer.wrap.style.top = event.clientY - from.y + "px"; + } - // Writing the X coordinate - target.style.left = - (parseInt(target.style.left) || 0) - change / 2 + "px"; + /** + * @name Restore + */ + function restore() { + // Restoring initial coordinates + layer.wrap.style.top = layer.wrap.style.left = null; + } - // Writing the Y coordinate - target.style.top = - (parseInt(target.style.top) || 0) - change / 2 + "px"; - } + /** + * @name Start + */ + function start(event) { + if (event.button === 0) { + // Pressed the main mouse button (left by default) - // Writing the scale - target.style.setProperty("--width-zoom", zoom + "px"); - } - } + // Writing the start moving cursor coordinates + [from.x, from.y] = [ + event.clientX - (parseInt(layer.wrap.style.left) || 0), + event.clientY - (parseInt(layer.wrap.style.top) || 0), + ]; - // Initializing the even listeners - target.addEventListener("wheel", scroll, false); - } + // Initializing the event listener + window.addEventListener("mousemove", moving, true); + } + } - /** - * @name Image - * - * @description - * Add the image into the canvas - * - * @param {File} file The file from input FileList - */ - image(file) { - // Initializing identifier - const identifier = this.#layers.entries.length ?? 1; + /** + * @name End + */ + function end() { + // Initializing the event listener + window.removeEventListener("mousemove", moving, true); + } - // Creating the layer
element - const layer = document.createElement("div"); - layer.classList.add("layer"); - layer.setAttribute("id", "pechatalka_layer_" + identifier); + // Initializing event listeners + layer.wrap.addEventListener("mousedown", start, false); + window.addEventListener("mouseup", end, false); + this.#canvas.addEventListener("mouseleave", end, false); + } - // Creating the button