Name change, registry stuff and started work on motion painter
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
+12
-6
@@ -6,15 +6,21 @@
|
||||
"""
|
||||
|
||||
from .nodes.flow import *
|
||||
from .nodes.motion_painter import *
|
||||
|
||||
# Mount web directory
|
||||
WEB_DIRECTORY = f"./web"
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"IG Flow Model Loader": IG_FlowModelLoader,
|
||||
"IG Flow Predictor": IG_FlowPredictor,
|
||||
"IG Flow Animator": IG_FlowAnimator,
|
||||
"MI2V Flow Model Loader": MI2V_FlowModelLoader,
|
||||
"MI2V Flow Predictor": MI2V_FlowPredictor,
|
||||
"MI2V Flow Animator": MI2V_FlowAnimator,
|
||||
"MI2V Motion Painter": MI2V_MotionPainter,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IG Flow Model Loader": "💾 IG Flow Model Loader",
|
||||
"IG Flow Predictor": "🌊 IG Flow Predictor",
|
||||
"IG Flow Animator": "🌊 IG Flow Animator",
|
||||
"MI2V Flow Model Loader": "💾 MI2V Flow Model Loader",
|
||||
"MI2V Flow Predictor": "🌊 MI2V Flow Predictor",
|
||||
"MI2V Flow Animator": "🌊 MI2V Flow Animator",
|
||||
"MI2V Motion Painter": "🎨 MI2V Motion Painter",
|
||||
}
|
||||
+3
-3
@@ -36,7 +36,7 @@ from ..animation.utils.convert_from_ckpt import (
|
||||
convert_ldm_vae_checkpoint,
|
||||
)
|
||||
|
||||
class IG_FlowModelLoader:
|
||||
class MI2V_FlowModelLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
@@ -140,7 +140,7 @@ class IG_FlowModelLoader:
|
||||
}
|
||||
return (flow_model,)
|
||||
|
||||
class IG_FlowPredictor:
|
||||
class MI2V_FlowPredictor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
@@ -244,7 +244,7 @@ class IG_FlowPredictor:
|
||||
return (flow_tensor.to(intermediate_device), flow_preview.to(intermediate_device),)
|
||||
|
||||
|
||||
class IG_FlowAnimator:
|
||||
class MI2V_FlowAnimator:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
import json
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import hashlib
|
||||
|
||||
# Directory to save node settings
|
||||
SETTINGS_DIR = os.path.join(os.path.dirname(__file__), "settings_nodes")
|
||||
os.makedirs(SETTINGS_DIR, exist_ok=True)
|
||||
|
||||
PREFIX = "_settings.json"
|
||||
|
||||
def is_valid_filename(filename):
|
||||
return filename and isinstance(filename, str) and filename.strip() != ""
|
||||
|
||||
def get_settings_filepath(filename):
|
||||
return os.path.join(SETTINGS_DIR, filename + PREFIX)
|
||||
|
||||
def load_settings(filename):
|
||||
filepath = get_settings_filepath(filename)
|
||||
if os.path.isfile(filepath):
|
||||
with open(filepath, "r") as f:
|
||||
try:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def save_settings(filename, data):
|
||||
filepath = get_settings_filepath(filename)
|
||||
with open(filepath, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
@PromptServer.instance.routes.get("/motion_vector/load_settings/{node_name}")
|
||||
async def load_settings_route(request):
|
||||
filename = request.match_info.get("node_name", None)
|
||||
data = load_settings(filename) if is_valid_filename(filename) else {}
|
||||
return web.json_response({"settings": data})
|
||||
|
||||
@PromptServer.instance.routes.post("/motion_vector/save_settings")
|
||||
async def save_settings_route(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
filename = data.get("name")
|
||||
settings = data.get("settings")
|
||||
if is_valid_filename(filename):
|
||||
save_settings(filename, settings)
|
||||
return web.json_response({"message": "Settings saved successfully"}, status=200)
|
||||
else:
|
||||
raise ValueError("Invalid filename")
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
class MI2V_MotionPainter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID"
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "VECTOR_DATA")
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Custom Nodes"
|
||||
|
||||
def execute(self, image, unique_id):
|
||||
# The actual processing will be handled on the client side.
|
||||
return (image, None)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, image, unique_id):
|
||||
# Use the hash of the image and unique ID to determine if the node has changed.
|
||||
m = hashlib.sha256()
|
||||
m.update(str(unique_id).encode('utf-8'))
|
||||
# Assuming 'image' is a filepath or an object that can be hashed.
|
||||
if isinstance(image, str) and os.path.isfile(image):
|
||||
with open(image, "rb") as f:
|
||||
m.update(f.read())
|
||||
return m.hexdigest()
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "comfyui-ig-motion-i2v"
|
||||
description = "ComfyUI adaptation of https://github.com/G-U-N/Motion-I2V"
|
||||
version = "0.5.0"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/IDGallagher/ComfyUI-IG-Motion-I2V"
|
||||
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
[tool.comfy]
|
||||
PublisherId = "iggy101"
|
||||
DisplayName = "ComfyUI-IG-Motion-I2V"
|
||||
Icon = ""
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,208 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { fabric } from "./lib/fabric.js";
|
||||
|
||||
console.log("HELLO WORLD");
|
||||
|
||||
function resizeCanvas(node, sizes)
|
||||
{
|
||||
const { width, height } = sizes ?? node.painter.currentCanvasSize;
|
||||
|
||||
node.painter.canvas.setDimensions({
|
||||
width: width,
|
||||
height: height,
|
||||
});
|
||||
|
||||
node.painter.canvas.getElement().width = width;
|
||||
node.painter.canvas.getElement().height = height;
|
||||
|
||||
node.painter.canvas.renderAll();
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
|
||||
class MotionPainter {
|
||||
constructor(node, canvas) {
|
||||
this.node = node;
|
||||
this.canvas = this.initCanvas(canvas);
|
||||
this.arrows = [];
|
||||
this.isDrawing = false;
|
||||
this.startPoint = null;
|
||||
this.currentCanvasSize = { width: 512, height: 512 };
|
||||
console.log("INIT");
|
||||
}
|
||||
|
||||
initCanvas(canvasEl) {
|
||||
this.canvas = new fabric.Canvas(canvasEl, {
|
||||
isDrawingMode: true,
|
||||
backgroundColor: "transparent",
|
||||
width: 512,
|
||||
height: 512,
|
||||
enablePointerEvents: true,
|
||||
});
|
||||
|
||||
|
||||
this.canvas.backgroundColor = "#ffffff";
|
||||
|
||||
fabric.util.addListener(
|
||||
this.canvas.upperCanvasEl,
|
||||
"contextmenu",
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
);
|
||||
|
||||
return this.canvas;
|
||||
}
|
||||
}
|
||||
|
||||
function MotionPainterWidget(node, app) {
|
||||
const widget = {
|
||||
type: "motion_painter_widget",
|
||||
name: `motion_painter_widget`,
|
||||
callback: () => {},
|
||||
draw: function (ctx, _, widgetWidth, y, widgetHeight)
|
||||
{
|
||||
const margin = 10,
|
||||
left_offset = 8,
|
||||
top_offset = 50,
|
||||
visible = app.canvas.ds.scale > 0.6 && this.type === "motion_painter_widget",
|
||||
w = widgetWidth - margin * 2 - 80,
|
||||
clientRectBound = ctx.canvas.getBoundingClientRect(),
|
||||
transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
clientRectBound.width / ctx.canvas.width,
|
||||
clientRectBound.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(margin, margin + y),
|
||||
scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
|
||||
|
||||
let aspect_ratio = 1;
|
||||
if (node?.imgs && typeof node.imgs !== undefined) {
|
||||
aspect_ratio = node.imgs[0].naturalHeight / node.imgs[0].naturalWidth;
|
||||
}
|
||||
|
||||
Object.assign(this.painter_wrap.style, {
|
||||
left: `${
|
||||
transform.a * margin * left_offset +
|
||||
transform.e +
|
||||
clientRectBound.left
|
||||
}px`,
|
||||
top: `${
|
||||
transform.d + transform.f + top_offset + clientRectBound.top
|
||||
}px`,
|
||||
width: `${w * transform.a}px`,
|
||||
height: `${w * transform.d}px`,
|
||||
position: "absolute",
|
||||
zIndex: app.graph._nodes.indexOf(node),
|
||||
});
|
||||
|
||||
Object.assign(this.painter_wrap.children[0].style, {
|
||||
transformOrigin: "0 0",
|
||||
transform: scale,
|
||||
width: w + "px",
|
||||
height: w * aspect_ratio + "px",
|
||||
});
|
||||
|
||||
Object.assign(this.painter_wrap.children[1].style, {
|
||||
transformOrigin: "0 0",
|
||||
transform: scale,
|
||||
width: w + "px",
|
||||
height: w * aspect_ratio + "px",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fabric canvas
|
||||
let canvasPainter = document.createElement("canvas");
|
||||
node.painter = new MotionPainter(node, canvasPainter)
|
||||
|
||||
node.painter.canvas.setWidth(node.painter.currentCanvasSize.width);
|
||||
node.painter.canvas.setHeight(node.painter.currentCanvasSize.height);
|
||||
|
||||
resizeCanvas(node, node.painter.canvas);
|
||||
|
||||
widget.painter_wrap = node.painter.canvas.wrapperEl;
|
||||
widget.parent = node;
|
||||
|
||||
document.body.appendChild(widget.painter_wrap);
|
||||
|
||||
// Add customWidget to node
|
||||
node.addCustomWidget(widget);
|
||||
|
||||
widget.onRemove = () => {
|
||||
widget.painter_wrap?.remove();
|
||||
};
|
||||
|
||||
node.onResize = function ()
|
||||
{
|
||||
let [w, h] = this.size;
|
||||
let aspect_ratio = 1;
|
||||
|
||||
if (node?.imgs && typeof this.imgs !== undefined) {
|
||||
aspect_ratio = this.imgs[0].naturalHeight / this.imgs[0].naturalWidth;
|
||||
}
|
||||
let buffer = 90;
|
||||
|
||||
if (w > this.painter.maxNodeSize) w = w - (w - this.painter.maxNodeSize);
|
||||
if (w < 600) w = 600;
|
||||
|
||||
h = w * aspect_ratio + buffer;
|
||||
|
||||
this.size = [w, h];
|
||||
};
|
||||
|
||||
app.canvas.onDrawBackground = function ()
|
||||
{
|
||||
// Draw node isnt fired once the node is off the screen
|
||||
// if it goes off screen quickly, the input may not be removed
|
||||
// this shifts it off screen so it can be moved back if the node is visible.
|
||||
for (let n in app.graph._nodes) {
|
||||
const currnode = app.graph._nodes[n];
|
||||
for (let w in currnode.widgets) {
|
||||
let wid = currnode.widgets[w];
|
||||
if (Object.hasOwn(wid, "motion_painter_widget")) {
|
||||
wid.painter_wrap.style.left = -8000 + "px";
|
||||
wid.painter_wrap.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
node.onResize();
|
||||
|
||||
return { widget: widget };
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "MI2V Motion Painter",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
|
||||
if (nodeData.name === "MI2V Motion Painter") {
|
||||
console.log("Registering onNodeCreated for MI2V_MotionPainter");
|
||||
// Create node
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const r = onNodeCreated
|
||||
? onNodeCreated.apply(this, arguments)
|
||||
: undefined;
|
||||
|
||||
console.log("onNodeCreated called for node:", this.type);
|
||||
// const node = this;
|
||||
|
||||
// // Initialize imageName property
|
||||
// node.imageName = "default_image.png"; // Set a default image name or path
|
||||
|
||||
// // Optionally, add a widget to select the image
|
||||
// node.addWidget("image", "image", null, (value) => {
|
||||
// node.imageName = value;
|
||||
// node.motionPainter.loadInputImage();
|
||||
// });
|
||||
|
||||
MotionPainterWidget.apply(this, [this, appInstance]);
|
||||
this.painter.canvas.renderAll();
|
||||
this.title = "HELLOOOO";
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+668
@@ -0,0 +1,668 @@
|
||||
// Make modal window
|
||||
function makeModal({
|
||||
title = "Message",
|
||||
text = "No text",
|
||||
type = "info",
|
||||
parent = null,
|
||||
stylePos = "fixed",
|
||||
} = {}) {
|
||||
const overlay = document.createElement("div");
|
||||
Object.assign(overlay.style, {
|
||||
display: "none",
|
||||
position: stylePos,
|
||||
background: "rgba(0 0 0 / 0.8)",
|
||||
opacity: 0,
|
||||
top: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
bottom: "0",
|
||||
zIndex: "500",
|
||||
transition: "all .8s",
|
||||
cursor: "pointer",
|
||||
});
|
||||
|
||||
const boxModal = document.createElement("div");
|
||||
Object.assign(boxModal.style, {
|
||||
transition: "all 0.5s",
|
||||
opacity: 0,
|
||||
display: "none",
|
||||
position: stylePos,
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%,-50%)",
|
||||
background: "#525252",
|
||||
minWidth: "300px",
|
||||
fontFamily: "sans-serif",
|
||||
zIndex: "501",
|
||||
border: "1px solid rgb(255 255 255 / 45%)",
|
||||
});
|
||||
|
||||
boxModal.className = "alekpet_modal_window";
|
||||
|
||||
const boxModalBody = document.createElement("div");
|
||||
Object.assign(boxModalBody.style, {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
textAlign: "center",
|
||||
});
|
||||
|
||||
boxModalBody.className = "alekpet_modal_body";
|
||||
|
||||
const boxModalHtml = `
|
||||
<div class="alekpet_modal_header" style="display: flex; align-items: center; background: #222; width: 100%;justify-content: center;">
|
||||
<div class="alekpet_modal_title" style="flex-basis: 85%; text-align: center;padding: 5px;">${title}</div>
|
||||
<div class="alekpet_modal_close">✕</div>
|
||||
</div>
|
||||
<div class="alekpet_modal_description" style="padding: 8px;">${text}</div>`;
|
||||
boxModalBody.innerHTML = boxModalHtml;
|
||||
|
||||
const alekpet_modal_header = boxModalBody.querySelector(
|
||||
".alekpet_modal_header"
|
||||
);
|
||||
Object.assign(alekpet_modal_header.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
const close = boxModalBody.querySelector(".alekpet_modal_close");
|
||||
Object.assign(close.style, {
|
||||
cursor: "pointer",
|
||||
});
|
||||
|
||||
let parentElement = document.body;
|
||||
if (parent && parent.nodeType === 1) {
|
||||
parentElement = parent;
|
||||
}
|
||||
|
||||
boxModal.append(boxModalBody);
|
||||
parentElement.append(overlay, boxModal);
|
||||
|
||||
const removeEvent = new Event("removeElements");
|
||||
const remove = () => {
|
||||
animateTransitionProps(boxModal, { opacity: 0 }).then(() =>
|
||||
animateTransitionProps(overlay, { opacity: 0 }).then(() => {
|
||||
parentElement.removeChild(boxModal);
|
||||
parentElement.removeChild(overlay);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
boxModal.addEventListener("removeElements", remove);
|
||||
overlay.addEventListener("removeElements", remove);
|
||||
|
||||
animateTransitionProps(overlay)
|
||||
.then(() => {
|
||||
overlay.addEventListener("click", () => {
|
||||
overlay.dispatchEvent(removeEvent);
|
||||
});
|
||||
animateTransitionProps(boxModal);
|
||||
})
|
||||
.then(() =>
|
||||
boxModal
|
||||
.querySelector(".alekpet_modal_close")
|
||||
.addEventListener("click", () => boxModal.dispatchEvent(removeEvent))
|
||||
);
|
||||
}
|
||||
|
||||
function findWidget(node, value, attr = "name", func = "find") {
|
||||
return node?.widgets
|
||||
? node.widgets[func]((w) =>
|
||||
Array.isArray(value) ? value.includes(w[attr]) : w[attr] === value
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
function animateTransitionProps(
|
||||
el,
|
||||
props = { opacity: 1 },
|
||||
preStyles = { display: "block" }
|
||||
) {
|
||||
Object.assign(el.style, preStyles);
|
||||
return new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
Object.assign(el.style, props);
|
||||
|
||||
const transstart = () => (el.isAnimating = true);
|
||||
const transchancel = () => (el.isAnimating = false);
|
||||
el.addEventListener("transitionstart", transstart);
|
||||
el.addEventListener("transitioncancel", transchancel);
|
||||
|
||||
el.addEventListener("transitionend", function transend() {
|
||||
el.isAnimating = false;
|
||||
el.removeEventListener("transitionend", transend);
|
||||
el.removeEventListener("transitionend", transchancel);
|
||||
el.removeEventListener("transitionend", transstart);
|
||||
res(el);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function animateClick(target, opacityVal = 0.9) {
|
||||
if (target?.isAnimating) return;
|
||||
|
||||
const hide = +target.style.opacity === 0;
|
||||
return animateTransitionProps(target, {
|
||||
opacity: hide ? opacityVal : 0,
|
||||
}).then(() => showHide({ elements: [target], hide: !hide }));
|
||||
}
|
||||
|
||||
function showHide({ elements = [], hide = null, displayProp = "block" } = {}) {
|
||||
Array.from(elements).forEach((el) => {
|
||||
if (hide !== null) {
|
||||
el.style.display = !hide ? displayProp : "none";
|
||||
} else {
|
||||
el.style.display =
|
||||
!el.style.display || el.style.display === "none" ? displayProp : "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isEmptyObject(obj) {
|
||||
if (!obj) return true;
|
||||
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||
}
|
||||
|
||||
function makeElement(tag, attrs = {}) {
|
||||
if (!tag) tag = "div";
|
||||
const element = document.createElement(tag);
|
||||
Object.keys(attrs).forEach((key) => {
|
||||
const currValue = attrs[key];
|
||||
if (key === "class") {
|
||||
if (Array.isArray(currValue)) {
|
||||
element.classList.add(...currValue);
|
||||
} else if (currValue instanceof String || typeof currValue === "string") {
|
||||
element.className = currValue;
|
||||
}
|
||||
} else if (key === "dataset") {
|
||||
try {
|
||||
if (Array.isArray(currValue)) {
|
||||
currValue.forEach((datasetArr) => {
|
||||
const [prop, propval] = Object.entries(datasetArr)[0];
|
||||
element.dataset[prop] = propval;
|
||||
});
|
||||
} else {
|
||||
Object.entries(currValue).forEach((datasetArr) => {
|
||||
const [prop, propval] = datasetArr;
|
||||
element.dataset[prop] = propval;
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
} else if (key === "style") {
|
||||
if (
|
||||
typeof currValue === "object" &&
|
||||
!Array.isArray(currValue) &&
|
||||
Object.keys(currValue).length
|
||||
) {
|
||||
Object.assign(element[key], currValue);
|
||||
} else if (
|
||||
typeof currValue === "object" &&
|
||||
Array.isArray(currValue) &&
|
||||
currValue.length
|
||||
) {
|
||||
element[key] = [...currValue];
|
||||
} else if (currValue instanceof String || typeof currValue === "string") {
|
||||
element[key] = currValue;
|
||||
}
|
||||
} else if (["for"].includes(key)) {
|
||||
element.setAttribute(key, currValue);
|
||||
} else if (key === "children") {
|
||||
element.append(...(currValue instanceof Array ? currValue : [currValue]));
|
||||
} else if (key === "parent") {
|
||||
currValue.append(element);
|
||||
} else {
|
||||
element[key] = currValue;
|
||||
}
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
function isValidStyle(opt, strColor) {
|
||||
let op = new Option().style;
|
||||
if (!op.hasOwnProperty(opt))
|
||||
return { result: false, color: "", color_hex: "" };
|
||||
|
||||
op[opt] = strColor;
|
||||
|
||||
return {
|
||||
result: op[opt] !== "",
|
||||
color_rgb: op[opt],
|
||||
color_hex: rgbToHex(op[opt]),
|
||||
};
|
||||
}
|
||||
|
||||
function rgbToHex(rgb) {
|
||||
const regEx = new RegExp(/\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
|
||||
if (regEx.test(rgb)) {
|
||||
let [, r, g, b] = regEx.exec(rgb);
|
||||
r = parseInt(r).toString(16);
|
||||
g = parseInt(g).toString(16);
|
||||
b = parseInt(b).toString(16);
|
||||
|
||||
r = r.length === 1 ? r + "0" : r;
|
||||
g = g.length === 1 ? g + "0" : g;
|
||||
b = b.length === 1 ? b + "0" : b;
|
||||
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function getDataJSON(url) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const jsonData = await response.json();
|
||||
return jsonData;
|
||||
} catch (err) {
|
||||
return new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function deepMerge(target, source) {
|
||||
if (source?.nodeType) return;
|
||||
for (let key in source) {
|
||||
if (source[key] instanceof Object && key in target) {
|
||||
Object.assign(source[key], deepMerge(target[key], source[key]));
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(target || {}, source);
|
||||
return target;
|
||||
}
|
||||
|
||||
const THEME_MODAL_WINDOW_BASE = {
|
||||
stylesTitle: {
|
||||
background: "auto",
|
||||
padding: "5px",
|
||||
borderRadius: "6px",
|
||||
marginBottom: "5px",
|
||||
alignSelf: "stretch",
|
||||
},
|
||||
stylesWrapper: {
|
||||
display: "none",
|
||||
opacity: 0,
|
||||
minWidth: "220px",
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
top: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
opacity: 0,
|
||||
transition: "all .8s",
|
||||
fontFamily: "monospace",
|
||||
zIndex: 99999,
|
||||
},
|
||||
stylesBox: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
background: "#0e0e0e",
|
||||
padding: "6px",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
textAlign: "center",
|
||||
borderRadius: "6px",
|
||||
color: "white",
|
||||
border: "2px solid silver",
|
||||
boxShadow: "2px 2px 4px silver",
|
||||
maxWidth: "300px",
|
||||
},
|
||||
stylesClose: {
|
||||
position: "absolute",
|
||||
top: "-10px",
|
||||
right: "-10px",
|
||||
background: "silver",
|
||||
borderRadius: "50%",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
fontSize: "0.8rem",
|
||||
},
|
||||
};
|
||||
|
||||
const THEMES_MODAL_WINDOW = {
|
||||
error: {
|
||||
stylesTitle: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesTitle,
|
||||
background: "#8f210f",
|
||||
},
|
||||
stylesBox: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesBox,
|
||||
background: "#3b2222",
|
||||
boxShadow: "3px 3px 6px #141414",
|
||||
border: "1px solid #f91b1b",
|
||||
},
|
||||
stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
|
||||
stylesClose: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesClose,
|
||||
background: "#3b2222",
|
||||
},
|
||||
},
|
||||
warning: {
|
||||
stylesTitle: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesTitle,
|
||||
background: "#e99818",
|
||||
},
|
||||
stylesBox: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesBox,
|
||||
background: "#594e32",
|
||||
boxShadow: "3px 3px 6px #141414",
|
||||
border: "1px solid #e99818",
|
||||
},
|
||||
stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
|
||||
stylesClose: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesClose,
|
||||
background: "#594e32",
|
||||
},
|
||||
},
|
||||
normal: {
|
||||
stylesTitle: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesTitle,
|
||||
background: "#108f0f",
|
||||
},
|
||||
stylesBox: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesBox,
|
||||
background: "#223b2a",
|
||||
boxShadow: "3px 3px 6px #141414",
|
||||
border: "1px solid #108f0f",
|
||||
},
|
||||
stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
|
||||
stylesClose: {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesClose,
|
||||
background: "#223b2a",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
auto: {
|
||||
autohide: false,
|
||||
autoshow: false,
|
||||
autoremove: false,
|
||||
propStyles: { opacity: 0 },
|
||||
propPreStyles: {},
|
||||
timewait: 2000,
|
||||
},
|
||||
overlay: {
|
||||
overlay_enabled: false,
|
||||
overlayClasses: [],
|
||||
overlayStyles: {},
|
||||
},
|
||||
close: { closeRemove: false, showClose: true },
|
||||
parent: null,
|
||||
};
|
||||
|
||||
function createWindowModal({
|
||||
textTitle = "Message",
|
||||
textBody = "Hello world!",
|
||||
textFooter = null,
|
||||
classesWrapper = [],
|
||||
stylesWrapper = {},
|
||||
classesBox = [],
|
||||
stylesBox = {},
|
||||
classesTitle = [],
|
||||
stylesTitle = {},
|
||||
classesBody = [],
|
||||
stylesBody = {},
|
||||
classesClose = [],
|
||||
stylesClose = {},
|
||||
classesFooter = [],
|
||||
stylesFooter = {},
|
||||
options = defaultOptions,
|
||||
} = {}) {
|
||||
// Check all options exist
|
||||
const _options = deepMerge(
|
||||
JSON.parse(JSON.stringify(defaultOptions)),
|
||||
options
|
||||
);
|
||||
|
||||
const {
|
||||
parent,
|
||||
overlay: { overlay_enabled, overlayClasses, overlayStyles },
|
||||
close: { closeRemove, showClose },
|
||||
auto: {
|
||||
autohide,
|
||||
autoshow,
|
||||
autoremove,
|
||||
timewait,
|
||||
propStyles,
|
||||
propPreStyles,
|
||||
},
|
||||
} = _options;
|
||||
|
||||
// Function past text(html)
|
||||
function addText(text, parent) {
|
||||
if (!parent) return;
|
||||
|
||||
switch (typeof text) {
|
||||
case "string":
|
||||
if (/^\<*.\/?\>$/.test(text)) {
|
||||
parent.innerHTML(text);
|
||||
} else {
|
||||
parent.textContent = text;
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
default:
|
||||
if (Array.isArray(text)) {
|
||||
text.forEach((element) => parent.append(element));
|
||||
} else if (text.nodeType === 1 || text.nodeType === 3)
|
||||
parent.append(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay
|
||||
let overlayElement = null;
|
||||
if (overlay_enabled) {
|
||||
overlayElement = makeElement("div", {
|
||||
class: [...overlayClasses],
|
||||
style: {
|
||||
display: "none",
|
||||
position: "fixed",
|
||||
background: "rgba(0 0 0 / 0.8)",
|
||||
opacity: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 99999,
|
||||
transition: "all .8s",
|
||||
cursor: "pointer",
|
||||
...overlayStyles,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Wrapper
|
||||
const wrapper_settings = makeElement("div", {
|
||||
class: ["alekpet__wrapper__window", ...classesWrapper],
|
||||
});
|
||||
|
||||
Object.assign(wrapper_settings.style, {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesWrapper,
|
||||
...stylesWrapper,
|
||||
});
|
||||
|
||||
// Box
|
||||
const box__settings = makeElement("div", {
|
||||
class: ["alekpet__window__box", ...classesBox],
|
||||
});
|
||||
Object.assign(box__settings.style, {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesBox,
|
||||
...stylesBox,
|
||||
});
|
||||
|
||||
// Title
|
||||
const box_settings_title = makeElement("div", {
|
||||
class: ["alekpet__window__title", ...classesTitle],
|
||||
});
|
||||
|
||||
Object.assign(box_settings_title.style, {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesTitle,
|
||||
...stylesTitle,
|
||||
});
|
||||
|
||||
// Add text (html) to title
|
||||
addText(textTitle, box_settings_title);
|
||||
|
||||
// Body
|
||||
const box_settings_body = makeElement("div", {
|
||||
class: ["alekpet__window__body", ...classesBody],
|
||||
});
|
||||
|
||||
Object.assign(box_settings_body.style, {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-end",
|
||||
gap: "5px",
|
||||
textWrap: "wrap",
|
||||
...stylesBody,
|
||||
});
|
||||
|
||||
// Add text (html) to body
|
||||
addText(textBody, box_settings_body);
|
||||
|
||||
// Close button
|
||||
const close__box__button = makeElement("div", {
|
||||
class: ["close__box__button", ...classesClose],
|
||||
textContent: "✖",
|
||||
});
|
||||
|
||||
Object.assign(close__box__button.style, {
|
||||
...THEME_MODAL_WINDOW_BASE.stylesClose,
|
||||
...stylesClose,
|
||||
});
|
||||
|
||||
if (!showClose) close__box__button.style.display = "none";
|
||||
|
||||
const closeEvent = new Event("closeModal");
|
||||
const closeModalWindow = function () {
|
||||
overlay_enabled
|
||||
? animateTransitionProps(overlayElement, {
|
||||
opacity: 0,
|
||||
})
|
||||
.then(() =>
|
||||
animateTransitionProps(wrapper_settings, {
|
||||
opacity: 0,
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
if (closeRemove) {
|
||||
parent.removeChild(wrapper_settings);
|
||||
parent.removeChild(overlayElement);
|
||||
} else {
|
||||
showHide({ elements: [wrapper_settings, overlayElement] });
|
||||
}
|
||||
})
|
||||
: animateTransitionProps(wrapper_settings, {
|
||||
opacity: 0,
|
||||
}).then(() => {
|
||||
showHide({ elements: [wrapper_settings] });
|
||||
});
|
||||
};
|
||||
|
||||
close__box__button.addEventListener("closeModal", closeModalWindow);
|
||||
|
||||
close__box__button.addEventListener("click", () =>
|
||||
close__box__button.dispatchEvent(closeEvent)
|
||||
);
|
||||
|
||||
close__box__button.onmouseenter = () => {
|
||||
close__box__button.style.opacity = 0.8;
|
||||
};
|
||||
|
||||
close__box__button.onmouseleave = () => {
|
||||
close__box__button.style.opacity = 1;
|
||||
};
|
||||
|
||||
box__settings.append(box_settings_title, box_settings_body);
|
||||
|
||||
// Footer
|
||||
if (textFooter) {
|
||||
const box_settings_footer = makeElement("div", {
|
||||
class: [...classesFooter],
|
||||
});
|
||||
Object.assign(box_settings_footer.style, {
|
||||
...stylesFooter,
|
||||
});
|
||||
|
||||
// Add text (html) to body
|
||||
addText(textFooter, box_settings_footer);
|
||||
|
||||
box__settings.append(box_settings_footer);
|
||||
}
|
||||
|
||||
wrapper_settings.append(close__box__button, box__settings);
|
||||
|
||||
if (parent && parent.nodeType === 1) {
|
||||
if (overlay_enabled) parent.append(overlayElement);
|
||||
parent.append(wrapper_settings);
|
||||
|
||||
if (autoshow) {
|
||||
overlay_enabled
|
||||
? animateClick(overlayElement).then(() =>
|
||||
animateClick(wrapper_settings).then(
|
||||
() =>
|
||||
autohide &&
|
||||
setTimeout(
|
||||
() =>
|
||||
animateTransitionProps(
|
||||
wrapper_settings,
|
||||
{ ...propStyles },
|
||||
{ ...propPreStyles }
|
||||
)
|
||||
.then(() =>
|
||||
animateTransitionProps(
|
||||
overlayElement,
|
||||
{ ...propStyles },
|
||||
{ ...propPreStyles }
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
if (autoremove) {
|
||||
parent.removeChild(wrapper_settings);
|
||||
parent.removeChild(overlayElement);
|
||||
}
|
||||
}),
|
||||
timewait
|
||||
)
|
||||
)
|
||||
)
|
||||
: animateClick(wrapper_settings).then(
|
||||
() =>
|
||||
autohide &&
|
||||
setTimeout(
|
||||
() =>
|
||||
animateTransitionProps(
|
||||
wrapper_settings,
|
||||
{ ...propStyles },
|
||||
{ ...propPreStyles }
|
||||
).then(
|
||||
() => autoremove && parent.removeChild(wrapper_settings)
|
||||
),
|
||||
timewait
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return wrapper_settings;
|
||||
}
|
||||
|
||||
export {
|
||||
makeModal,
|
||||
createWindowModal,
|
||||
animateTransitionProps,
|
||||
animateClick,
|
||||
showHide,
|
||||
makeElement,
|
||||
getDataJSON,
|
||||
isEmptyObject,
|
||||
isValidStyle,
|
||||
rgbToHex,
|
||||
findWidget,
|
||||
THEMES_MODAL_WINDOW,
|
||||
};
|
||||
Reference in New Issue
Block a user