Add miniPaint as new frontend base

This commit is contained in:
motion
2026-01-26 12:20:16 -05:00
parent bb859105ff
commit 6ae4cbcb77
306 changed files with 60729 additions and 4050 deletions
+518
View File
@@ -0,0 +1,518 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../config.js';
import Base_layers_class from './base-layers.js';
import GUI_tools_class from './gui/gui-tools.js';
import GUI_preview_class from './gui/gui-preview.js';
import GUI_colors_class from './gui/gui-colors.js';
import GUI_layers_class from './gui/gui-layers.js';
import GUI_information_class from './gui/gui-information.js';
import GUI_details_class from './gui/gui-details.js';
import GUI_menu_class from './gui/gui-menu.js';
import Tools_translate_class from './../modules/tools/translate.js';
import Tools_settings_class from './../modules/tools/settings.js';
import Helper_class from './../libs/helpers.js';
import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js';
var instance = null;
/**
* Main GUI class
*/
class Base_gui_class {
constructor() {
//singleton
if (instance) {
return instance;
}
instance = this;
this.Helper = new Helper_class();
this.Base_layers = new Base_layers_class();
//last used menu id
this.last_menu = '';
//grid dimensions config
this.grid_size = [50, 50];
//if grid is visible
this.grid = false;
this.canvas_offset = {x: 0, y: 0};
//common image dimensions
this.common_dimensions = [
[640, 480, '480p'],
[800, 600, 'SVGA'],
[1024, 768, 'XGA'],
[1280, 720, 'hdtv, 720p'],
[1600, 1200, 'UXGA'],
[1920, 1080, 'Full HD, 1080p'],
[3840, 2160, '4K UHD'],
//[7680,4320, '8K UHD'],
];
this.GUI_tools = new GUI_tools_class(this);
this.GUI_preview = new GUI_preview_class(this);
this.GUI_colors = new GUI_colors_class(this);
this.GUI_layers = new GUI_layers_class(this);
this.GUI_information = new GUI_information_class(this);
this.GUI_details = new GUI_details_class(this);
this.GUI_menu = new GUI_menu_class();
this.Tools_translate = new Tools_translate_class();
this.Tools_settings = new Tools_settings_class();
this.modules = {};
}
init() {
this.load_modules();
this.load_default_values();
this.render_main_gui();
this.init_service_worker();
}
load_modules() {
var _this = this;
var modules_context = require.context("./../modules/", true, /\.js$/);
modules_context.keys().forEach(function (key) {
if (key.indexOf('Base' + '/') < 0) {
var moduleKey = key.replace('./', '').replace('.js', '');
var classObj = modules_context(key);
_this.modules[moduleKey] = new classObj.default();
}
});
}
load_default_values() {
//transparency
var transparency_cookie = this.Helper.getCookie('transparency');
if (transparency_cookie === null) {
//default
config.TRANSPARENCY = false;
}
if (transparency_cookie) {
config.TRANSPARENCY = true;
}
else {
config.TRANSPARENCY = false;
}
//transparency_type
var transparency_type = this.Helper.getCookie('transparency_type');
if (transparency_type === null) {
//default
config.TRANSPARENCY_TYPE = 'squares';
}
if (transparency_type) {
config.TRANSPARENCY_TYPE = transparency_type;
}
//snap
var snap_cookie = this.Helper.getCookie('snap');
if (snap_cookie === null) {
//default
config.SNAP = true;
}
else{
config.SNAP = Boolean(snap_cookie);
}
//guides
var guides_cookie = this.Helper.getCookie('guides');
if (guides_cookie === null) {
//default
config.guides_enabled = true;
}
else{
config.guides_enabled = Boolean(guides_cookie);
}
}
render_main_gui() {
this.autodetect_dimensions();
this.change_theme();
this.prepare_canvas();
this.GUI_tools.render_main_tools();
this.GUI_preview.render_main_preview();
this.GUI_colors.render_main_colors();
this.GUI_layers.render_main_layers();
this.GUI_information.render_main_information();
this.GUI_details.render_main_details();
this.GUI_menu.render_main();
this.load_saved_changes();
this.set_events();
this.load_translations();
}
init_service_worker() {
/*if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js').then(function(reg) {
//Successfully registered service worker
}).catch(function(err) {
console.warn('Error registering service worker', err);
});
}*/
}
set_events() {
var _this = this;
//menu events
this.GUI_menu.on('select_target', (target, object) => {
var parts = target.split('.');
var module = parts[0];
var function_name = parts[1];
var param = object.parameter ??= null;
//call module
if (this.modules[module] == undefined) {
alertify.error('Modules class not found: ' + module);
return;
}
if (this.modules[module][function_name] == undefined) {
alertify.error('Module function not found. ' + module + '.' + function_name);
return;
}
this.modules[module][function_name](param);
});
//registerToggleAbility
var targets = document.querySelectorAll('.toggle');
for (var i = 0; i < targets.length; i++) {
if (targets[i].dataset.target == undefined)
continue;
targets[i].addEventListener('click', function (event) {
this.classList.toggle('toggled');
var target = document.getElementById(this.dataset.target);
target.classList.toggle('hidden');
//save
if (target.classList.contains('hidden') == false)
_this.Helper.setCookie(this.dataset.target, 1);
else
_this.Helper.setCookie(this.dataset.target, 0);
});
}
document.getElementById('left_mobile_menu_button').addEventListener('click', function (event) {
document.querySelector('.sidebar_left').classList.toggle('active');
});
document.getElementById('mobile_menu_button').addEventListener('click', function (event) {
document.querySelector('.sidebar_right').classList.toggle('active');
});
window.addEventListener('resize', function (event) {
//resize
_this.prepare_canvas();
config.need_render = true;
}, false);
this.check_canvas_offset();
//confirmation on exit
var exit_confirm = this.Tools_settings.get_setting('exit_confirm');
window.addEventListener('beforeunload', function (e) {
if(exit_confirm && (config.layers.length > 1 || _this.Base_layers.is_layer_empty(config.layer.id) == false)){
e.preventDefault();
e.returnValue = '';
}
return undefined;
});
document.getElementById('canvas_minipaint').addEventListener('contextmenu', function (e) {
e.preventDefault();
}, false);
}
check_canvas_offset() {
//calc canvas position offset
var bodyRect = document.body.getBoundingClientRect();
var canvas_el = document.getElementById('canvas_minipaint').getBoundingClientRect();
this.canvas_offset.x = canvas_el.left - bodyRect.left;
this.canvas_offset.y = canvas_el.top - bodyRect.top;
}
prepare_canvas() {
var canvas = document.getElementById('canvas_minipaint');
var ctx = canvas.getContext("2d");
var wrapper = document.getElementById('main_wrapper');
var page_w = wrapper.clientWidth;
var page_h = wrapper.clientHeight;
var w = Math.min(Math.ceil(config.WIDTH * config.ZOOM), page_w);
var h = Math.min(Math.ceil(config.HEIGHT * config.ZOOM), page_h);
canvas.width = w;
canvas.height = h;
config.visible_width = w;
config.visible_height = h;
if(config.ZOOM >= 1) {
ctx.imageSmoothingEnabled = false;
}
else{
ctx.imageSmoothingEnabled = true;
}
this.render_canvas_background('canvas_minipaint');
//change wrapper dimensions
document.getElementById('canvas_wrapper').style.width = w + 'px';
document.getElementById('canvas_wrapper').style.height = h + 'px';
this.check_canvas_offset();
}
load_saved_changes() {
var targets = document.querySelectorAll('.toggle');
for (var i = 0; i < targets.length; i++) {
if (targets[i].dataset.target == undefined)
continue;
var target = document.getElementById(targets[i].dataset.target);
var saved = this.Helper.getCookie(targets[i].dataset.target);
if (saved === 0) {
targets[i].classList.toggle('toggled');
target.classList.add('hidden');
}
}
}
load_translations() {
var lang = this.Helper.getCookie('language');
//load from params
var params = this.Helper.get_url_parameters();
if(params.lang != undefined){
lang = params.lang.replace(/([^a-z]+)/gi, '');
}
if (lang != null && lang != config.LANG) {
config.LANG = lang.replace(/([^a-z]+)/gi, '');
this.Tools_translate.translate(config.LANG);
}
}
autodetect_dimensions() {
var wrapper = document.getElementById('main_wrapper');
var page_w = wrapper.clientWidth;
var page_h = wrapper.clientHeight;
var auto_size = false;
//use largest possible
for (var i = this.common_dimensions.length - 1; i >= 0; i--) {
if (this.common_dimensions[i][0] > page_w
|| this.common_dimensions[i][1] > page_h) {
//browser size is too small
continue;
}
config.WIDTH = parseInt(this.common_dimensions[i][0]);
config.HEIGHT = parseInt(this.common_dimensions[i][1]);
auto_size = true;
break;
}
if (auto_size == false) {
//screen size is smaller then 400x300
config.WIDTH = parseInt(page_w) - 15;
config.HEIGHT = parseInt(page_h) - 10;
}
}
render_canvas_background(canvas_id, gap) {
if (gap == undefined)
gap = 10;
var target = document.getElementById(canvas_id + '_background');
if (config.TRANSPARENCY == false) {
target.className = 'transparent-grid white';
return false;
}
else{
target.className = 'transparent-grid ' + config.TRANSPARENCY_TYPE;
}
target.style.backgroundSize = (gap * 2) + 'px auto';
}
draw_grid(ctx) {
if (this.grid == false)
return;
var gap_x = this.grid_size[0];
var gap_y = this.grid_size[1];
var width = config.WIDTH;
var height = config.HEIGHT;
//size
if (gap_x != undefined && gap_y != undefined)
this.grid_size = [gap_x, gap_y];
else {
gap_x = this.grid_size[0];
gap_y = this.grid_size[1];
}
gap_x = parseInt(gap_x);
gap_y = parseInt(gap_y);
ctx.lineWidth = 1;
ctx.beginPath();
if (gap_x < 2)
gap_x = 2;
if (gap_y < 2)
gap_y = 2;
for (var i = gap_x; i < width; i = i + gap_x) {
if (gap_x == 0)
break;
if (i % (gap_x * 5) == 0) {
//main lines
ctx.strokeStyle = '#222222';
}
else {
//small lines
ctx.strokeStyle = '#bbbbbb';
}
ctx.beginPath();
ctx.moveTo(0.5 + i, 0);
ctx.lineTo(0.5 + i, height);
ctx.stroke();
}
for (var i = gap_y; i < height; i = i + gap_y) {
if (gap_y == 0)
break;
if (i % (gap_y * 5) == 0) {
//main lines
ctx.strokeStyle = '#222222';
}
else {
//small lines
ctx.strokeStyle = '#bbbbbb';
}
ctx.beginPath();
ctx.moveTo(0, 0.5 + i);
ctx.lineTo(width, 0.5 + i);
ctx.stroke();
}
}
draw_guides(ctx){
if(config.guides_enabled == false){
return;
}
var thick_guides = this.Tools_settings.get_setting('thick_guides');
for(var i in config.guides) {
var guide = config.guides[i];
if (guide.x === 0 || guide.y === 0) {
continue;
}
//set styles
ctx.strokeStyle = '#00b8b8';
if(thick_guides == false)
ctx.lineWidth = 1;
else
ctx.lineWidth = 3;
ctx.beginPath();
if (guide.y === null) {
//vertical
ctx.moveTo(guide.x, 0);
ctx.lineTo(guide.x, config.HEIGHT);
}
if (guide.x === null) {
//horizontal
ctx.moveTo(0, guide.y);
ctx.lineTo(config.WIDTH, guide.y);
}
ctx.stroke();
}
}
/**
* change draw area size
*
* @param {int} width
* @param {int} height
*/
set_size(width, height) {
config.WIDTH = parseInt(width);
config.HEIGHT = parseInt(height);
this.prepare_canvas();
}
/**
*
* @returns {object} keys: width, height
*/
get_visible_area_size() {
var wrapper = document.getElementById('main_wrapper');
var page_w = wrapper.clientWidth;
var page_h = wrapper.clientHeight;
//find visible size in pixels, but make sure its correct even if image smaller then screen
var w = Math.min(Math.ceil(config.WIDTH * config.ZOOM), Math.ceil(page_w / config.ZOOM));
var h = Math.min(Math.ceil(config.HEIGHT * config.ZOOM), Math.ceil(page_h / config.ZOOM));
return {
width: w,
height: h,
};
}
/**
* change theme or set automatically from cookie if possible
*
* @param {string} theme_name
*/
change_theme(theme_name = null){
if(theme_name == null){
//auto detect
var theme_cookie = this.Helper.getCookie('theme');
if (theme_cookie) {
theme_name = theme_cookie;
}
else {
theme_name = this.Tools_settings.get_setting('theme');
}
}
for(var i in config.themes){
document.querySelector('body').classList.remove('theme-' + config.themes[i]);
}
document.querySelector('body').classList.add('theme-' + theme_name);
}
get_language() {
return config.LANG;
}
get_color() {
return config.COLOR;
}
get_alpha() {
return config.ALPHA;
}
get_zoom() {
return config.ZOOM;
}
get_transparency_support() {
return config.TRANSPARENCY;
}
get_active_tool() {
return config.TOOL;
}
}
export default Base_gui_class;
+884
View File
@@ -0,0 +1,884 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import app from "./../app.js";
import config from "./../config.js";
import Base_gui_class from "./base-gui.js";
import Base_selection_class from "./base-selection.js";
import Image_trim_class from "./../modules/image/trim.js";
import View_ruler_class from "./../modules/view/ruler.js";
import zoomView from "./../libs/zoomView.js";
import Helper_class from "./../libs/helpers.js";
import alertify from "./../../../node_modules/alertifyjs/build/alertify.min.js";
var instance = null;
/**
* Layers class - manages layers. Each layer is object with various types. Keys:
* - id (int)
* - link (image)
* - parent_id (int)
* - name (string)
* - type (string)
* - x (int)
* - y (int)
* - width (int)
* - height (int)
* - width_original (int)
* - height_original (int)
* - visible (bool)
* - is_vector (bool)
* - hide_selection_if_active (bool)
* - opacity (0-100)
* - order (int)
* - composition (string)
* - rotate (int) 0-359
* - data (various data here)
* - params (object)
* - color {hex}
* - status (string)
* - filters (array)
* - render_function (function)
*/
class Base_layers_class {
constructor() {
//singleton
if (instance) {
return instance;
}
instance = this;
this.Base_gui = new Base_gui_class();
this.Helper = new Helper_class();
this.Image_trim = new Image_trim_class();
this.View_ruler = new View_ruler_class();
this.canvas = document.getElementById("canvas_minipaint");
this.ctx = document.getElementById("canvas_minipaint").getContext("2d");
this.ctx_preview = document
.getElementById("canvas_preview")
.getContext("2d");
this.last_zoom = 1;
this.auto_increment = 1;
this.stable_dimensions = [];
this.debug_rendering = false;
this.render_success = null;
this.disabled_filter_id = null;
}
/**
* do preparation on start
*/
init() {
this.init_zoom_lib();
new app.Actions.Insert_layer_action({}).do();
var sel_config = {
enable_background: false,
enable_borders: true,
enable_controls: false,
enable_rotation: false,
enable_move: false,
data_function: function () {
return config.layer;
},
};
this.Base_selection = new Base_selection_class(
this.ctx,
sel_config,
"main"
);
this.render(true);
}
init_zoom_lib() {
zoomView.setBounds(0, 0, config.WIDTH, config.HEIGHT);
zoomView.setContext(this.ctx);
this.stable_dimensions = [config.WIDTH, config.HEIGHT];
}
pre_render() {
this.ctx.save();
zoomView.canvasDefault();
this.ctx.clearRect(
0,
0,
config.WIDTH * config.ZOOM,
config.HEIGHT * config.ZOOM
);
}
after_render() {
config.need_render = false;
config.need_render_changed_params = false;
this.ctx.restore();
zoomView.canvasDefault();
}
/**
* renders all layers objects on main canvas
*
* @param {bool} force
*/
render(force) {
var _this = this;
if (force !== true) {
//request render and exit
config.need_render = true;
return;
}
if (
this.stable_dimensions[0] != config.WIDTH ||
this.stable_dimensions[1] != config.HEIGHT
) {
//dimensions changed - re-init zoom lib
this.init_zoom_lib();
}
if (config.need_render == true) {
this.render_success = null;
if (this.debug_rendering === true) {
console.log("Rendering...");
}
if (this.last_zoom != config.ZOOM) {
//change zoom
zoomView.scaleAt(
this.Base_gui.GUI_preview.zoom_data.x,
this.Base_gui.GUI_preview.zoom_data.y,
config.ZOOM / this.last_zoom
);
} else if (this.Base_gui.GUI_preview.zoom_data.move_pos != null) {
//move visible window
var pos = this.Base_gui.GUI_preview.zoom_data.move_pos;
var pos_global = zoomView.toScreen(pos);
zoomView.move(-pos_global.x, -pos_global.y);
this.Base_gui.GUI_preview.zoom_data.move_pos = null;
}
//prepare
this.pre_render();
//take data
var layers_sorted = this.get_sorted_layers();
zoomView.apply();
const newCanvas = this.create_new_canvas(
null,
config.WIDTH,
config.HEIGHT
);
this.render_objects(this.ctx, newCanvas, layers_sorted, ()=>{
this.ctx.save();
});
//grid
this.Base_gui.draw_grid(this.ctx);
//guides
this.Base_gui.draw_guides(this.ctx);
//render selected object controls
this.Base_selection.draw_selection();
//active tool overlay
this.render_overlay();
//render preview
this.render_preview(layers_sorted);
//reset
this.after_render();
this.last_zoom = config.ZOOM;
this.Base_gui.GUI_details.render_details();
this.View_ruler.render_ruler();
if (this.render_success === false) {
alertify.error("Rendered with errors.");
}
}
requestAnimationFrame(function () {
_this.render(force);
});
}
render_overlay() {
var render_class = config.TOOL.name;
var render_function = "render_overlay";
if (
typeof this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
] != "undefined"
) {
this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
](this.ctx);
}
}
/**
* LEGACY: use create_new_canvas();
*/
createNewCanvas(ctx, h, w) {
this.create_new_canvas(ctx, w, h);
}
/**
* Creates a fresh new canvas with the same height and width as the provided one
* @param {canvas.context|null} ctx
* @param {number} [width]
* @param {number} [height]
*/
create_new_canvas(ctx, width, height) {
const newCanvas = document.createElement("canvas");
if(width){
newCanvas.width = width;
}
else{
newCanvas.width = ctx.canvas.width;
}
if(height){
newCanvas.height = height;
}
else{
newCanvas.height = ctx.canvas.height;
}
return newCanvas;
}
/**
* LEGACY: use render_objects()
*/
renderObjects(ctx, tempCanvas, layers, prepare, shouldSkip) {
this.render_objects(ctx, tempCanvas, layers, prepare, shouldSkip);
}
/**
* Renders objects based on the provided layers
* @param {canvas.context} ctx - Main canvas context where it needs to be rendered
* @param {canvas} tempCanvas - A temporary canvas which is a copy of the original canvas, but will be used if there will be needed to isolate an effect from others
* @param {Object[]} layers - Array of layers
* @param {Function} prepare - An optional function to prepare temporary and main canvases before the render if needed
* @param {Function} shouldSkip - An optional boolean function for skipping those layers which are not needed to be rendered
*/
render_objects(ctx, tempCanvas, layers, prepare, shouldSkip) {
const tempCtx = tempCanvas.getContext("2d");
// Prepare the temporary canvas if needed
prepare && prepare();
for (var i = layers.length - 1; i >= 0; i--) {
var layer = layers[i];
const nextLayer = layers[i - 1];
// If the previous layer has clip masking effect and the current one is not the other end of the pair,
// then render the temporary canvas for clip masking on top of the current.
// Skip the layer if not needed to be rendered
if (shouldSkip && shouldSkip(layer)) {
continue;
}
// If the layer or next layer has clip masking effect (source-atop).
// If there are such layers, this will make sure that layers will be rendered
// in an isolated temporary canvas
if (
layer.composition === "source-atop" ||
(nextLayer && nextLayer.composition === "source-atop")
) {
// Apply the effect in a isolated temporary canvas
tempCtx.globalAlpha = layer.opacity / 100;
tempCtx.globalCompositeOperation = layer.composition;
// If the next layer has the clip masking effect then
// isolated the shadow filter from temporary canvas and keep that in the original canvas
if (nextLayer?.composition === "source-atop") {
// Render the layer
this.render_object(ctx, layer);
// Then remove the shadow (if it exists) from the render process in the temporary canvas
const filters = layer.filters.filter((filter) => {
return filter.name !== "shadow";
});
this.render_object(tempCtx, {
...layer,
filters,
});
} else {
// If we are in this condition, then it means this is the last layer of clipped layers pair.
// Render clipped layers on the temporary canvas
this.render_object(tempCtx, layer);
// Render the clipped layers on top of the current canvas
ctx.restore();
ctx.drawImage(tempCanvas, 0, 0);
// Prepare canvas to since we called restore
prepare && prepare();
// Clear temporary canvas
tempCtx.globalCompositeOperation = null;
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
}
} else {
ctx.globalAlpha = layer.opacity / 100;
ctx.globalCompositeOperation = layer.composition;
this.render_object(ctx, layer);
}
}
}
render_preview(layers) {
var w = this.Base_gui.GUI_preview.PREVIEW_SIZE.w;
var h = this.Base_gui.GUI_preview.PREVIEW_SIZE.h;
this.ctx_preview.save();
this.ctx_preview.clearRect(0, 0, w, h);
const newCanvas = this.create_new_canvas(this.ctx_preview);
newCanvas.getContext("2d").scale(w / config.WIDTH, h / config.HEIGHT);
this.render_objects(this.ctx_preview, newCanvas, layers, () => {
this.ctx_preview.save();
//prepare scale
this.ctx_preview.scale(w / config.WIDTH, h / config.HEIGHT);
});
this.ctx_preview.restore();
this.Base_gui.GUI_preview.render_preview_active_zone();
}
/**
* export current layers to given canvas
*
* @param {canvas.context} ctx
* @param {object} object
* @param {boolean} is_preview
*/
render_object(ctx, object, is_preview) {
if (object.visible == false || object.type == null) return;
this.pre_render_object(ctx, object);
//example with canvas object - other types should overwrite this method
if (object.type == "image") {
//image - default behavior
ctx.save();
ctx.translate(object.x + object.width / 2, object.y + object.height / 2);
ctx.rotate((object.rotate * Math.PI) / 180);
// TODO - Not sure why the check should be with null,
// if nothing will break, then better to check if it's just truthy
ctx.drawImage(
object.link_canvas != null ? object.link_canvas : object.link,
-object.width / 2,
-object.height / 2,
object.width,
object.height
);
ctx.restore();
} else {
//call render function from other module
var render_class = object.render_function[0];
var render_function = object.render_function[1];
if (
typeof this.Base_gui.GUI_tools.tools_modules[render_class] !=
"undefined"
) {
this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
](ctx, object, is_preview);
} else {
this.render_success = false;
console.log("Error: unknown layer type: " + object.type);
}
}
this.after_render_object(ctx, object);
}
/**
* Gets called before render_object starts it's job
* @param {canvas.context} ctx
* @param {object} object
*/
pre_render_object(ctx, object) {
//apply pre-filters
for (var i in object.filters) {
var filter = object.filters[i];
if (filter.id == this.disabled_filter_id) {
continue;
}
filter.name = filter.name.replace("drop-shadow", "shadow");
//find filter
var found = false;
for (var i in this.Base_gui.modules) {
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1) continue;
var filter_class = this.Base_gui.modules[i];
var module_name = i.split("/").pop();
if (module_name == filter.name) {
//found it
found = true;
filter_class.render_pre(ctx, filter, object);
}
}
if (found == false) {
this.render_success = false;
console.log("Error: can not find filter: " + filter.name);
}
}
}
/**
* Gets called after when render_object finishes it's job
* @param {canvas.context} ctx
* @param {object} object
*/
after_render_object(ctx, object) {
//apply post-filters
for (var i in object.filters) {
var filter = object.filters[i];
if (filter.id == this.disabled_filter_id) {
continue;
}
filter.name = filter.name.replace("drop-shadow", "shadow");
//find filter
var found = false;
for (var i in this.Base_gui.modules) {
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1) continue;
var filter_class = this.Base_gui.modules[i];
var module_name = i.split("/").pop();
if (module_name == filter.name) {
//found it
found = true;
filter_class.render_post(ctx, filter, object);
}
}
if (found == false) {
this.render_success = false;
console.log("Error: can not find filter: " + filter.name);
}
}
}
/**
* creates new layer
*
* @param {array} settings
* @param {boolean} can_automate
*/
async insert(settings, can_automate = true) {
return app.State.do_action(
new app.Actions.Insert_layer_action(settings, can_automate)
);
}
/**
* autoresize layer, based on dimensions, up - always, if 1 layer - down.
*
* @param {int} width
* @param {int} height
* @param {int} layer_id
* @param {boolean} can_automate
*/
async autoresize(width, height, layer_id, can_automate = true) {
return app.State.do_action(
new app.Actions.Autoresize_canvas_action(
width,
height,
layer_id,
can_automate
)
);
}
/**
* returns layer
*
* @param {int} id
* @returns {object}
*/
get_layer(id) {
if (id == undefined) {
id = config.layer.id;
}
for (var i in config.layers) {
if (config.layers[i].id == id) {
return config.layers[i];
}
}
alertify.error("Error: can not find layer with id:" + id);
return null;
}
/**
* removes layer
*
* @param {int} id
* @param {boolean} force - Force to delete first layer?
*/
async delete(id, force) {
return app.State.do_action(new app.Actions.Delete_layer_action(id, force));
}
/*
* removes all layers
*/
async reset_layers(auto_insert) {
return app.State.do_action(
new app.Actions.Reset_layers_action(auto_insert)
);
}
/**
* toggle layer visibility
*
* @param {int} id
*/
async toggle_visibility(id) {
return app.State.do_action(
new app.Actions.Toggle_layer_visibility_action(id)
);
}
/*
* renew layers HTML
*/
refresh_gui() {
this.Base_gui.GUI_layers.render_layers();
}
/**
* marks layer as selected, active
*
* @param {int} id
*/
async select(id) {
return app.State.do_action(new app.Actions.Select_layer_action(id));
}
/**
* change layer opacity
*
* @param {int} id
* @param {int} value 0-100
*/
async set_opacity(id, value) {
value = parseInt(value);
if (value < 0 || value > 100) {
//reset
value = 100;
}
return app.State.do_action(
new app.Actions.Update_layer_action(id, {
opacity: value,
})
);
}
/**
* clear layer data
*
* @param {int} id
*/
async layer_clear(id) {
return app.State.do_action(new app.Actions.Clear_layer_action(id));
}
/**
* move layer up or down
*
* @param {int} id
* @param {int} direction
*/
async move(id, direction) {
return app.State.do_action(
new app.Actions.Reorder_layer_action(id, direction)
);
}
/**
* clone and sort.
*/
get_sorted_layers() {
return config.layers.concat().sort(
//sort function
(a, b) => b.order - a.order
);
}
/**
* checks if layer empty
*
* @param {int} id
* @returns {Boolean}
*/
is_layer_empty(id) {
var link = this.get_layer(id);
if (
(link.width == 0 || link.width === null) &&
(link.height == 0 || link.height === null) &&
link.data == null
) {
return true;
}
return false;
}
/**
* find next layer
*
* @param {int} id layer id
* @returns {layer|null}
*/
find_next(id) {
id = parseInt(id);
var link = this.get_layer(id);
var layers_sorted = this.get_sorted_layers();
var last = null;
for (var i = layers_sorted.length - 1; i >= 0; i--) {
var value = layers_sorted[i];
if (last != null && last.id == link.id) {
return value;
}
last = value;
}
return null;
}
/**
* find previous layer
*
* @param {int} id layer id
* @returns {layer|null}
*/
find_previous(id) {
id = parseInt(id);
var link = this.get_layer(id);
var layers_sorted = this.get_sorted_layers();
var last = null;
for (var i in layers_sorted) {
var value = layers_sorted[i];
if (last != null && last.id == link.id) {
return value;
}
last = value;
}
return null;
}
/**
* returns global position, for example if canvas is zoomed, it will convert relative mouse position to absolute
* at 100% zoom.
*
* @param {int} x
* @param {int} y
* @returns {object} keys: x, y
*/
get_world_coords(x, y) {
return zoomView.toWorld(x, y);
}
/**
* register new live filter
*
* @param {int} layer_id
* @param {string} name
* @param {object} params
*/
add_filter(layer_id, name, params) {
return app.State.do_action(
new app.Actions.Add_layer_filter_action(layer_id, name, params)
);
}
/**
* delete live filter
*
* @param {int} layer_id
* @param {string} filter_id
*/
delete_filter(layer_id, filter_id) {
return app.State.do_action(
new app.Actions.Delete_layer_filter_action(layer_id, filter_id)
);
}
/**
* exports all layers to canvas for saving
*
* @param {canvas.context} ctx
* @param {int} layer_id Optional
* @param {boolean} is_preview Optional
*/
convert_layers_to_canvas(ctx, layer_id = null, is_preview = true) {
const newCanvas = this.create_new_canvas(ctx);
const layers_sorted = this.get_sorted_layers();
this.render_objects(ctx, newCanvas, layers_sorted, ()=>{
ctx.save();
}, (value) => {
if (value.visible == false || value.type == null) {
return true;
}
if (layer_id != null && value.id != layer_id) {
return true;
}
});
}
/**
* exports (active) layer to canvas for saving
*
* @param {int} layer_id or current layer by default
* @param {boolean} actual_area used for resized image. Default is false.
* @param {boolean} can_trim default is true
* @returns {canvas}
*/
convert_layer_to_canvas(layer_id, actual_area = false, can_trim) {
if (actual_area == null) actual_area = false;
if (layer_id == null) layer_id = config.layer.id;
var link = this.get_layer(layer_id);
var offset_x = 0;
var offset_y = 0;
//create tmp canvas
var canvas = document.createElement("canvas");
if (actual_area === true && link.type == "image") {
canvas.width = link.width_original;
canvas.height = link.height_original;
can_trim = false;
} else {
canvas.width = Math.max(link.width, config.WIDTH);
canvas.height = Math.max(link.height, config.HEIGHT);
}
//add data
if (actual_area === true && link.type == "image") {
canvas.getContext("2d").drawImage(link.link, 0, 0);
} else {
this.render_object(canvas.getContext("2d"), link);
}
//trim
if ((can_trim == true || can_trim == undefined) && link.type != null) {
var trim_info = this.Image_trim.get_trim_info(layer_id);
if (
trim_info.left > 0 ||
trim_info.top > 0 ||
trim_info.right > 0 ||
trim_info.bottom > 0
) {
offset_x = trim_info.left;
offset_y = trim_info.top;
var w = canvas.width - trim_info.left - trim_info.right;
var h = canvas.height - trim_info.top - trim_info.bottom;
if (w > 1 && h > 1) {
this.Helper.change_canvas_size(canvas, w, h, offset_x, offset_y);
}
}
}
canvas.dataset.x = offset_x;
canvas.dataset.y = offset_y;
return canvas;
}
/**
* updates layer image data
*
* @param {canvas} canvas
* @param {int} layer_id (optional)
*/
update_layer_image(canvas, layer_id) {
return app.State.do_action(
new app.Actions.Update_layer_image_action(canvas, layer_id)
);
}
/**
* returns canvas dimensions.
*
* @returns {object}
*/
get_dimensions() {
return {
width: config.WIDTH,
height: config.HEIGHT,
};
}
/**
* returns all layers
*
* @returns {array}
*/
get_layers() {
return config.layers;
}
/**
* disabled filter by id
*
* @param filter_id
*/
disable_filter(filter_id) {
this.disabled_filter_id = filter_id;
}
/**
* finds layer filter by filter ID
*
* @param filter_id
* @param filter_name
* @param layer_id
* @returns {object}
*/
find_filter_by_id(filter_id, filter_name, layer_id) {
if (typeof layer_id == "undefined") {
var layer = config.layer;
} else {
var layer = this.get_layer(layer_id);
}
var filter = {};
for (var i in layer.filters) {
if (
layer.filters[i].name == filter_name &&
layer.filters[i].id == filter_id
) {
return layer.filters[i].params;
}
}
return filter;
}
}
export default Base_layers_class;
+166
View File
@@ -0,0 +1,166 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../config.js';
import Dialog_class from './../libs/popup.js';
import Base_gui_class from './base-gui.js';
const fuzzysort = require('fuzzysort');
var instance = null;
class Base_search_class {
constructor() {
//singleton
if (instance) {
return instance;
}
instance = this;
this.POP = new Dialog_class();
this.Base_gui = new Base_gui_class();
this.db = null;
this.events();
}
events() {
document.addEventListener('keydown', (event) => {
if (this.POP.get_active_instances() > 0) {
return;
}
var code = event.key;
if (code == "F3" || ( (event.ctrlKey == true || event.metaKey) && code == "f")) {
//open
this.search();
event.preventDefault();
}
}, false);
document.addEventListener('input', (event) => {
if(document.querySelector('#pop_data_search') == null){
return;
}
var node = document.querySelector('#global_search_results');
node.innerHTML = '';
var query = event.target.value;
if(query == ''){
return;
}
let results = fuzzysort.go(query, this.db, {
keys: ['title'],
limit: 10,
threshold: -50000,
});
//show
for(var i = 0; i < results.length; i++) {
var item = results[i];
var className = "search-result n" + (i+1);
if(i == 0){
className += " active";
}
node.innerHTML += "<div class='"+className+"' data-key='"+item.obj.key+"'>"
+ fuzzysort.highlight(item[0]) + "</div>";
}
}, false);
//allow to select with arrow keys
document.addEventListener('keydown', function (e) {
if(document.querySelector('#global_search_results') == null
|| document.querySelector('.search-result') == null){
return;
}
var k = e.key;
if (k == "ArrowUp") {
var target = document.querySelector('.search-result.active');
var index = Array.from(target.parentNode.children).indexOf(target);
if(index > 0){
index--;
}
target.classList.remove('active');
var target2 =document.querySelector('#global_search_results').childNodes[index];
target2.classList.add('active');
e.preventDefault();
}
else if (k == "ArrowDown") {
var target = document.querySelector('.search-result.active');
var index = Array.from(target.parentNode.children).indexOf(target);
var total = target.parentNode.childElementCount;
if(index < total - 1){
index++;
}
target.classList.remove('active');
var target2 = document.querySelector('#global_search_results').childNodes[index];
target2.classList.add('active');
e.preventDefault();
}
}, false);
}
search() {
var _this = this;
//init DB
if(this.db === null) {
this.db = Object.keys(this.Base_gui.modules);
for(var i in this.db){
this.db[i] = {
key: this.db[i],
title: this.db[i].replace(/_/i, ' '),
};
}
}
var settings = {
title: 'Search',
params: [
{name: "search", title: "Search:", value: ""},
],
on_load: function (params, popup) {
var node = document.createElement("div");
node.id = 'global_search_results';
node.innerHTML = '';
popup.el.querySelector('.dialog_content').appendChild(node);
},
on_finish: function (params) {
//execute
var target = document.querySelector('.search-result.active');
if(target){
//execute
var key = target.dataset.key;
var class_object = this.Base_gui.modules[key];
var function_name = _this.get_function_from_path(key);
_this.POP.hide();
class_object[function_name]();
}
},
};
this.POP.show(settings);
//on input change
document.getElementById("pop_data_search").select();
}
get_function_from_path(path){
var parts = path.split("/");
var result = parts[parts.length - 1];
result = result.replace(/-/, '_');
return result;
}
}
export default Base_search_class;
+558
View File
@@ -0,0 +1,558 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../config.js';
var instance = null;
var settings_all = [];
const handle_size = 12;
const DRAG_TYPE_TOP = 1;
const DRAG_TYPE_BOTTOM = 2;
const DRAG_TYPE_LEFT = 4;
const DRAG_TYPE_RIGHT = 8;
/**
* Selection class - draws rectangular selection on canvas, can be resized.
*/
class Base_selection_class {
/**
* settings:
* - enable_background
* - enable_borders
* - enable_controls
* - enable_rotation
* - enable_move
* - keep_ratio
*
* @param {ctx} ctx
* @param {object} settings
* @param {string|null} key
*/
constructor(ctx, settings, key = null) {
if (key != null) {
settings_all[key] = settings;
}
//singleton
if (instance) {
return instance;
}
instance = this;
this.ctx = ctx;
this.mouse_lock = null;
this.selected_obj_positions = {};
this.selected_obj_rotate_position = {};
this.selected_object_drag_type = null;
this.click_details = {};
this.is_touch = false;
// True if dragging from inside canvas area
this.is_drag = false;
this.current_angle = null;
this.events();
}
events() {
document.addEventListener('mousedown', (e) => {
this.is_drag = false;
if(this.is_touch == true)
return;
if (!e.target.closest('#main_wrapper'))
return;
this.is_drag = true;
this.selected_object_actions(e);
});
document.addEventListener('mousemove', (e) => {
if(this.is_touch == true)
return;
this.selected_object_actions(e);
});
document.addEventListener('mouseup', (e) => {
if(this.is_touch == true)
return;
this.selected_object_actions(e);
});
// touch
document.addEventListener('touchstart', (event) => {
this.is_drag = false;
this.is_touch = true;
if (!event.target.closest('#main_wrapper'))
return;
this.is_drag = true;
this.selected_object_actions(event);
});
document.addEventListener('touchmove', (event) => {
this.selected_object_actions(event);
}, {passive: false});
document.addEventListener('touchend', (event) => {
this.selected_object_actions(event);
});
}
set_selection(x, y, width, height) {
var settings = this.find_settings();
if (x != null)
settings.data.x = x;
if (y != null)
settings.data.y = y;
if (width != null)
settings.data.width = width;
if (height != null)
settings.data.height = height;
config.need_render = true;
}
reset_selection() {
var settings = this.find_settings();
settings.data = {
x: null,
y: null,
width: null,
height: null,
};
config.need_render = true;
}
get_selection() {
var settings = this.find_settings();
return settings.data;
}
find_settings() {
var current_key = config.TOOL.name;
var settings = null;
for (var i in settings_all) {
if (i == current_key)
settings = settings_all[i];
}
//default
if (settings === null) {
settings = settings_all['main'];
}
//find data
settings.data = (settings.data_function).call();
return settings;
}
calcRotateDistanceFromX(layerW) {
const block_size = handle_size / config.ZOOM;
return Math.max(
Math.min(layerW * 0.9, Math.abs(layerW - 2 * block_size)),
layerW / 2 - block_size / 2
);
}
/**
* marks object as selected, and draws corners
*/
draw_selection() {
var settings = this.find_settings();
var data = settings.data;
if (settings.data === null || settings.data.status == 'draft'
|| (settings.data.hide_selection_if_active === true && settings.data.type == config.TOOL.name)) {
return;
}
var x = settings.data.x;
var y = settings.data.y;
var w = settings.data.width;
var h = settings.data.height;
if (x == null || y == null || w == null || h == null) {
//not supported
return;
}
var block_size_default = handle_size / config.ZOOM;
if (config.ZOOM != 1) {
x = Math.round(x);
y = Math.round(y);
w = Math.round(w);
h = Math.round(h);
}
var block_size = block_size_default;
var corner_offset = (block_size / 2.4);
var middle_offset = (block_size / 1.9);
this.ctx.save();
this.ctx.globalAlpha = 1;
let isRotated = false;
if (data.rotate != null && data.rotate != 0) {
//rotate
isRotated = true;
this.ctx.translate(data.x + data.width / 2, data.y + data.height / 2);
this.ctx.rotate(data.rotate * Math.PI / 180);
x = Math.round(-data.width / 2);
y = Math.round(-data.height / 2);
}
//fill
if (settings.enable_background == true) {
this.ctx.fillStyle = "rgba(0, 255, 0, 0.3)";
this.ctx.fillRect(x, y, w, h);
}
const wholeLineWidth = 2 / config.ZOOM;
const halfLineWidth = wholeLineWidth / 2;
//borders
if (settings.enable_borders == true && (x != 0 || y != 0 || w != config.WIDTH || h != config.HEIGHT)) {
this.ctx.lineWidth = wholeLineWidth;
this.ctx.strokeStyle = 'rgb(255, 255, 255)';
this.ctx.strokeRect(x - halfLineWidth, y - halfLineWidth, w + wholeLineWidth, h + wholeLineWidth);
this.ctx.lineWidth = halfLineWidth;
this.ctx.strokeStyle = 'rgb(0, 0, 0)';
this.ctx.strokeRect(x - wholeLineWidth, y - wholeLineWidth, w + (wholeLineWidth * 2), h + (wholeLineWidth * 2));
}
//show crop lines
if(settings.crop_lines === true){
for(var part = 1; part < 3; part++) {
this.ctx.lineWidth = wholeLineWidth;
this.ctx.strokeStyle = 'rgb(255, 255, 255)';
this.ctx.beginPath();
this.ctx.moveTo(x + w / 3 * part - halfLineWidth, y);
this.ctx.lineTo(x + w / 3 * part - halfLineWidth, y + h);
this.ctx.stroke();
this.ctx.lineWidth = halfLineWidth;
this.ctx.strokeStyle = 'rgb(0, 0, 0)';
this.ctx.beginPath();
this.ctx.moveTo(x + w / 3 * part - halfLineWidth, y);
this.ctx.lineTo(x + w / 3 * part - halfLineWidth, y + h);
this.ctx.stroke();
}
for(var part = 1; part < 3; part++) {
this.ctx.lineWidth = wholeLineWidth;
this.ctx.strokeStyle = 'rgb(255, 255, 255)';
this.ctx.beginPath();
this.ctx.moveTo(x, y + h / 3 * part - halfLineWidth);
this.ctx.lineTo(x + w, y + h / 3 * part - halfLineWidth);
this.ctx.stroke();
this.ctx.lineWidth = halfLineWidth;
this.ctx.strokeStyle = 'rgb(0, 0, 0)';
this.ctx.beginPath();
this.ctx.moveTo(x, y + h / 3 * part - halfLineWidth);
this.ctx.lineTo(x + w, y + h / 3 * part - halfLineWidth);
this.ctx.stroke();
}
}
const hitsLeftEdge = isRotated ? false : x < handle_size;
const hitsTopEdge = isRotated ? false : y < handle_size;
const hitsRightEdge = isRotated ? false : x + w > config.WIDTH - handle_size;
const hitsBottomEdge = isRotated ? false : y + h > config.HEIGHT - handle_size;
//draw corners
var corner = (x, y, dx, dy, drag_type, cursor) => {
var angle = 0;
if (settings.data.rotate != null && settings.data.rotate != 0) {
angle = settings.data.rotate;
}
if (settings.enable_controls == false || angle != 0) {
this.ctx.strokeStyle = "rgba(0, 0, 0, 0.4)";
this.ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
}
else {
this.ctx.strokeStyle = "#000000";
this.ctx.fillStyle = "#ffffff";
}
this.ctx.lineWidth = wholeLineWidth;
//create path
const circle = new Path2D();
circle.arc(x + dx * block_size, y + dy * block_size, block_size / 2, 0, 2 * Math.PI);
//draw
this.ctx.fill(circle);
this.ctx.stroke(circle);
//register position
this.selected_obj_positions[drag_type] = {
cursor: cursor,
path: circle,
};
};
//draw rotation
var draw_rotation = () => {
var settings = this.find_settings();
if (settings.data === null
|| settings.data.status == 'draft'
|| settings.data.rotate === null
|| (settings.data.hide_selection_if_active === true && settings.data.type == config.TOOL.name)) {
return;
}
var r_x = x + this.calcRotateDistanceFromX(w) + corner_offset + wholeLineWidth;
var r_y = y - corner_offset - wholeLineWidth;
var r_dx = hitsRightEdge ? -0.5 : 0;
var r_dy = hitsTopEdge ? 0.5 : 0;
this.ctx.strokeStyle = "#000000";
this.ctx.fillStyle = "#d0d62a";
this.ctx.lineWidth = wholeLineWidth;
//create path
const circle = new Path2D();
circle.arc(r_x + r_dx * block_size, r_y + r_dy * block_size, block_size / 2, 0, 2 * Math.PI);
//draw
this.ctx.fill(circle);
this.ctx.stroke(circle);
//register position
this.selected_obj_rotate_position = {
cursor: "pointer",
path: circle,
};
};
if (settings.enable_rotation == true) {
draw_rotation();
}
if (settings.enable_controls == true) {
corner(x - corner_offset - wholeLineWidth, y - corner_offset - wholeLineWidth, hitsLeftEdge ? 0.5 : 0, hitsTopEdge ? 0.5 : 0, DRAG_TYPE_LEFT | DRAG_TYPE_TOP, 'nwse-resize');
corner(x + w + corner_offset + wholeLineWidth, y - corner_offset - wholeLineWidth, hitsRightEdge ? -0.5 : 0, hitsTopEdge ? 0.5 : 0, DRAG_TYPE_RIGHT | DRAG_TYPE_TOP, 'nesw-resize');
corner(x - corner_offset - wholeLineWidth, y + h + corner_offset + wholeLineWidth, hitsLeftEdge ? 0.5 : 0, hitsBottomEdge ? -0.5 : 0, DRAG_TYPE_LEFT | DRAG_TYPE_BOTTOM, 'nesw-resize');
corner(x + w + corner_offset + wholeLineWidth, y + h + corner_offset + wholeLineWidth, hitsRightEdge ? -0.5 : 0, hitsBottomEdge ? -0.5 : 0, DRAG_TYPE_RIGHT | DRAG_TYPE_BOTTOM, 'nwse-resize');
}
if (settings.enable_controls == true) {
//draw centers
if (Math.abs(w) > block_size * 5) {
corner(x + w / 2, y - middle_offset - wholeLineWidth, 0, hitsTopEdge ? 0.5 : 0, DRAG_TYPE_TOP, 'ns-resize');
corner(x + w / 2, y + h + middle_offset + wholeLineWidth, 0, hitsBottomEdge ? -0.5 : 0, DRAG_TYPE_BOTTOM, 'ns-resize');
}
if (Math.abs(h) > block_size * 5) {
corner(x - middle_offset - wholeLineWidth, y + h / 2, hitsLeftEdge ? 0.5 : 0, 0, DRAG_TYPE_LEFT, 'ew-resize');
corner(x + w + middle_offset + wholeLineWidth, y + h / 2, hitsRightEdge ? -0.5 : 0, 0, DRAG_TYPE_RIGHT, 'ew-resize');
}
}
//restore
this.ctx.restore();
}
selected_object_actions(e) {
var settings = this.find_settings();
var data = settings.data;
if(data == null){
return;
}
this.ctx.save();
if (data.rotate != null && data.rotate != 0) {
this.ctx.translate(data.x + data.width / 2, data.y + data.height / 2);
this.ctx.rotate(data.rotate * Math.PI / 180);
}
var x = settings.data.x;
var y = settings.data.y;
var w = settings.data.width;
var h = settings.data.height;
//simplify checks
var event_type = e.type;
if(event_type == 'touchstart') event_type = 'mousedown';
if(event_type == 'touchmove') event_type = 'mousemove';
if(event_type == 'touchend') event_type = 'mouseup';
if (!this.is_drag && ['mousedown', 'mouseup'].includes(event_type))
return;
const mainWrapper = document.getElementById('main_wrapper');
const defaultCursor = config.TOOL && config.TOOL.name === 'text' ? 'text' : 'default';
if (mainWrapper.style.cursor != defaultCursor) {
mainWrapper.style.cursor = defaultCursor;
}
if (event_type == 'mousedown' && config.mouse.valid == false || settings.enable_controls == false) {
return;
}
var mouse = config.mouse;
const drag_type = this.selected_object_drag_type;
if(event_type == 'mousedown' && settings.data !== null){
this.click_details = {
x: settings.data.x,
y: settings.data.y,
width: settings.data.width,
height: settings.data.height,
};
this.current_angle = null;
}
if (event_type == 'mousemove' && this.mouse_lock == 'selected_object_actions' && this.is_drag) {
const allowNegativeDimensions = settings.data.render_function
&& ['line', 'arrow', 'gradient'].includes(settings.data.render_function[0]);
mainWrapper.style.cursor = "pointer";
var is_ctrl = false;
if (e.ctrlKey == true || e.metaKey) {
is_ctrl = true;
}
const is_drag_type_left = Math.floor(drag_type / DRAG_TYPE_LEFT) % 2 === 1;
const is_drag_type_right = Math.floor(drag_type / DRAG_TYPE_RIGHT) % 2 === 1;
const is_drag_type_top = Math.floor(drag_type / DRAG_TYPE_TOP) % 2 === 1;
const is_drag_type_bottom = Math.floor(drag_type / DRAG_TYPE_BOTTOM) % 2 === 1;
if(is_drag_type_left && is_drag_type_top) mainWrapper.style.cursor = "nwse-resize";
else if(is_drag_type_top && is_drag_type_right) mainWrapper.style.cursor = "nesw-resize";
else if(is_drag_type_right && is_drag_type_bottom) mainWrapper.style.cursor = "nwse-resize";
else if(is_drag_type_bottom && is_drag_type_left) mainWrapper.style.cursor = "nesw-resize";
else if(is_drag_type_top) mainWrapper.style.cursor = "ns-resize";
else if(is_drag_type_right) mainWrapper.style.cursor = "ew-resize";
else if(is_drag_type_bottom) mainWrapper.style.cursor = "ns-resize";
else if(is_drag_type_left) mainWrapper.style.cursor = "ew-resize";
if(drag_type == 'rotate'){
//rotate
var dx = x + this.calcRotateDistanceFromX(w) - (x + w / 2);
var dy = h / 2;
var original_angle = Math.atan2(dy, dx) / Math.PI * 180; //compensate rotation icon angle
var dx = mouse.x - (x + w / 2);
var dy = mouse.y - (y + h / 2);
var angle = Math.atan2(dy, dx) / Math.PI * 180 + original_angle;
//settings.data.rotate = angle;
this.current_angle = angle;
config.need_render = true;
}
else if (e.buttons == 1 || typeof e.buttons == "undefined") {
// Do transformations
var dx = Math.round(mouse.x - mouse.click_x);
var dy = Math.round(mouse.y - mouse.click_y);
var width = this.click_details.width + dx;
var height = this.click_details.height + dy;
if (is_drag_type_top)
height = this.click_details.height - dy;
if (is_drag_type_left)
width = this.click_details.width - dx;
// Keep ratio - (if drag_type power of 2, only dragging on single axis)
if (drag_type && (drag_type & (drag_type - 1)) !== 0 && (settings.keep_ratio == true && is_ctrl == false)
|| (settings.keep_ratio !== true && is_ctrl == true)){
var ratio = this.click_details.width / this.click_details.height;
var width_new = Math.round(height * ratio);
var height_new = Math.round(width / ratio);
if (Math.abs(width * 100 / width_new) > Math.abs(height * 100 / height_new)) {
height = height_new;
}
else {
width = width_new;
}
}
// Set values
settings.data.x = this.click_details.x;
settings.data.y = this.click_details.y;
if (is_drag_type_top)
settings.data.y = this.click_details.y - (height - this.click_details.height);
if (is_drag_type_left)
settings.data.x = this.click_details.x - (width - this.click_details.width);
if (is_drag_type_left || is_drag_type_right)
settings.data.width = width;
if (is_drag_type_top || is_drag_type_bottom)
settings.data.height = height;
// Don't allow negative width/height on most layers
if (!allowNegativeDimensions) {
if (settings.data.width <= 0) {
settings.data.width = Math.abs(settings.data.width);
if (is_drag_type_left) {
settings.data.x -= settings.data.width;
} else {
settings.data.x = this.click_details.x - settings.data.width;
}
}
if (settings.data.height <= 0) {
settings.data.height = Math.abs(settings.data.height);
if (is_drag_type_top) {
settings.data.y -= settings.data.height;
} else {
settings.data.y = this.click_details.y - settings.data.height;
}
}
}
config.need_render = true;
}
return;
}
if (event_type == 'mouseup' && this.mouse_lock == 'selected_object_actions') {
//reset
this.mouse_lock = null;
}
if (!this.mouse_lock) {
//set mouse move cursor
if(settings.enable_move && mouse.x > x && mouse.x < x + w && mouse.y > y && mouse.y < y + h){
mainWrapper.style.cursor = "move";
}
for (let current_drag_type in this.selected_obj_positions) {
const position = this.selected_obj_positions[current_drag_type];
if (position.path && this.ctx.isPointInPath(position.path, mouse.x, mouse.y)) {
// match
if (event_type == 'mousedown') {
if (e.buttons == 1 || typeof e.buttons == "undefined") {
this.mouse_lock = 'selected_object_actions';
this.selected_object_drag_type = current_drag_type;
}
}
if (event_type == 'mousemove') {
mainWrapper.style.cursor = position.cursor;
}
}
}
//rotate?
const position = this.selected_obj_rotate_position;
if (position.path && this.ctx.isPointInPath(position.path, mouse.x, mouse.y)) {
//match
if (event_type == 'mousedown') {
if (e.buttons == 1 || typeof e.buttons == "undefined") {
this.mouse_lock = 'selected_object_actions';
this.selected_object_drag_type = "rotate";
}
}
if (event_type == 'mousemove') {
mainWrapper.style.cursor = position.cursor;
}
}
this.ctx.restore();
}
}
}
export default Base_selection_class;
+222
View File
@@ -0,0 +1,222 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../config.js';
import Base_layers_class from './base-layers.js';
import Base_gui_class from './base-gui.js';
import Helper_class from './../libs/helpers.js';
import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js';
import app from '../app.js';
var instance = null;
/**
* Undo state class. Supports multiple levels undo.
*/
class Base_state_class {
constructor() {
//singleton
if (instance) {
return instance;
}
instance = this;
this.Base_layers = new Base_layers_class();
this.Base_gui = new Base_gui_class();
this.Helper = new Helper_class();
this.layers_archive = [];
this.levels = 3;
this.levels_optimal = 3;
this.enabled = true;
this.action_history = [];
this.action_history_index = 0;
this.action_history_max = 50;
this.set_events();
}
set_events() {
document.addEventListener('keydown', (event) => {
const key = (event.key || '').toLowerCase();
if (this.Helper.is_input(event.target))
return;
if (key == "z" && (event.ctrlKey == true || event.metaKey)) {
// Undo
this.undo();
event.preventDefault();
}
if (key == "y" && (event.ctrlKey == true || event.metaKey)) {
// Redo
this.redo();
event.preventDefault();
}
}, false);
}
async do_action(action, options = {}) {
let error_during_free = false;
try {
await action.do();
} catch (error) {
// Action aborted. This is usually expected behavior as actions throw errors if they shouldn't run.
return { status: 'aborted', reason: error };
}
// Remove all redo actions from history
if (this.action_history_index < this.action_history.length) {
const freed_actions = this.action_history.slice(this.action_history_index, this.action_history.length).reverse();
this.action_history = this.action_history.slice(0, this.action_history_index);
for (let freed_action of freed_actions) {
try {
await freed_action.free();
} catch (error) {
error_during_free = true;
}
}
}
// Add the new action to history
const last_action = this.action_history[this.action_history.length - 1];
if (options.merge_with_history && last_action) {
if (typeof options.merge_with_history === 'string') {
options.merge_with_history = [options.merge_with_history];
}
if (options.merge_with_history.includes(last_action.action_id)) {
this.action_history[this.action_history.length - 1] = new app.Actions.Bundle_action(
last_action.action_id,
last_action.action_description,
[last_action, action]
);
}
} else {
this.action_history.push(action);
if (this.action_history.length > this.action_history_max) {
let action_to_free = this.action_history.shift();
try {
await action_to_free.free();
} catch (error) {
error_during_free = true;
}
} else {
this.action_history_index++;
}
}
// Chrome arbitrary method to determine memory usage, but most people use Chrome so...
if (window.performance && window.performance.memory) {
if (window.performance.memory.usedJSHeapSize > window.performance.memory.jsHeapSizeLimit * 0.8) {
this.free(window.performance.memory.jsHeapSizeLimit * 0.2);
}
}
if (error_during_free) {
alertify.error('A problem occurred while removing undo history. It\'s suggested you save your work and refresh the page in order to free up memory.');
}
return { status: 'completed' };
}
can_redo() {
return this.action_history_index < this.action_history.length;
}
can_undo() {
return this.action_history_index > 0;
}
async redo_action() {
if (this.can_redo()) {
const action = this.action_history[this.action_history_index];
await action.do();
this.action_history_index++;
} else {
alertify.success('There\'s nothing to redo', 3);
}
}
async undo_action() {
if (this.can_undo()) {
this.action_history_index--;
await this.action_history[this.action_history_index].undo();
} else {
alertify.success('There\'s nothing to undo', 3);
}
}
async scrap_last_action() {
if (this.can_undo()) {
await this.undo_action();
this.action_history.pop();
}
}
// Frees history actions up to the specified memory & database size. Starts with undo history, then moves to redo history.
async free(memory_size = 0, database_size = 0) {
let total_memory_freed = 0;
let total_database_freed = 0;
let has_error = false;
let free_complete = false;
while (this.action_history_index > 0) {
let action = this.action_history.shift();
total_memory_freed += action.memory_estimate;
total_database_freed += action.database_estimate;
try {
await action.free();
} catch (error) {
has_error = true;
}
if (total_memory_freed >= memory_size && total_database_freed >= database_size) {
free_complete = true;
break;
}
this.action_history_index--;
}
if (!free_complete) {
for (let i = this.action_history.length - 1; i >= 0; i--) {
let action = this.action_history[i];
total_memory_freed += action.memory_estimate;
total_database_freed += action.database_estimate;
try {
await action.free();
} catch (error) {
has_error = true;
}
if (total_memory_freed >= memory_size && total_database_freed >= database_size) {
free_complete = true;
break;
}
}
}
if (has_error) {
alertify.error('A problem occurred while removing undo history. It\'s suggested you save your work and refresh the page in order to free up memory.');
}
return {
total_memory_freed,
total_database_freed
}
}
save() {
const message = 'window.State.save() is removed. Use State.do_action() to manage undo history instead.';
console.warn(message);
alertify.error(message);
}
/**
* supports multiple levels undo system
*/
undo() {
this.undo_action();
}
/**
* supports multiple levels redo system
*/
redo() {
this.redo_action();
}
}
export default Base_state_class;
+734
View File
@@ -0,0 +1,734 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../config.js';
import Base_layers_class from './base-layers.js';
import Base_gui_class from './base-gui.js';
import app from "../app";
import Helper_class from "../libs/helpers";
/**
* Base tools class, can be used for extending on tools like brush, provides various helping methods.
*/
class Base_tools_class {
constructor(save_mouse) {
this.Base_layers = new Base_layers_class();
this.Base_gui = new Base_gui_class();
this.Helper = new Helper_class();
this.is_drag = false;
this.mouse_last_click_pos = [false, false];
this.mouse_click_pos = [false, false];
this.mouse_move_last = [false, false];
this.mouse_valid = false;
this.mouse_click_valid = false;
this.speed_average = 0;
this.save_mouse = save_mouse;
this.is_touch = false;
this.shape_mouse_click = {x: null, y: null};
this.prepare();
if (this.save_mouse == true) {
this.events();
}
}
dragStart(event) {
var _this = this;
var mouse = _this.get_mouse_info(event, true);
_this.mouse_click_pos[0] = mouse.x;
_this.mouse_click_pos[1] = mouse.y;
//update
_this.set_mouse_info(event);
_this.is_drag = true;
_this.speed_average = 0;
var mouse = _this.get_mouse_info(event, true);
_this.mouse_last_click_pos[0] = mouse.x;
_this.mouse_last_click_pos[1] = mouse.y;
}
dragMove(event) {
var _this = this;
_this.set_mouse_info(event);
_this.speed_average = _this.calc_average_mouse_speed(event);
}
dragEnd(event) {
var _this = this;
_this.is_drag = false;
_this.set_mouse_info(event);
}
events() {
var _this = this;
//collect mouse info
document.addEventListener('mousedown', function (event) {
if(_this.is_touch == true)
return;
_this.dragStart(event);
});
document.addEventListener('mousemove', function (event) {
if(_this.is_touch == true)
return;
_this.dragMove(event);
});
document.addEventListener('mouseup', function (event) {
if(_this.is_touch == true)
return;
_this.dragEnd(event);
});
// collect touch info
document.addEventListener('touchstart', function (event) {
_this.is_touch = true;
_this.dragStart(event);
});
document.addEventListener('touchmove', function (event) {
_this.dragMove(event);
if (event.target.id === "canvas_minipaint" && !$('.scroll').has($(event.target)).length)
event.preventDefault();
}, {passive: false});
document.addEventListener('touchend', function (event) {
_this.dragEnd(event);
});
//on resize
window.addEventListener('resize', function (event) {
_this.prepare();
});
}
/**
* do preparation
*/
prepare() {
this.is_drag = config.mouse.is_drag;
}
set_mouse_info(event) {
if (this.save_mouse !== true) {
//not main
return false;
}
var eventType = event.type;
if (event.target.id != 'canvas_minipaint' && event.target.id != 'main_wrapper') {
//outside canvas
this.mouse_valid = false;
}
else {
this.mouse_valid = true;
}
if (eventType === 'mousedown' || eventType === 'touchstart') {
if ((event.target.id != 'canvas_minipaint' && event.target.id != 'main_wrapper') || (event.which != 1 && eventType !== 'touchstart')) {
this.mouse_click_valid = false;
}
else {
this.mouse_click_valid = true;
}
this.mouse_valid = true;
}
if (event.changedTouches) {
//using touch events
event = event.changedTouches[0];
}
var mouse_coords = this.get_mouse_coordinates_from_event(event);
var mouse_x = mouse_coords.x;
var mouse_y = mouse_coords.y;
var start_pos = this.Base_layers.get_world_coords(0, 0);
var x_rel = mouse_x - start_pos.x;
var y_rel = mouse_y - start_pos.y;
//save
config.mouse = {
x: mouse_x,
y: mouse_y,
x_rel: x_rel,
y_rel: y_rel,
last_click_x: this.mouse_last_click_pos[0], //last click
last_click_y: this.mouse_last_click_pos[1], //last click
click_x: this.mouse_click_pos[0],
click_y: this.mouse_click_pos[1],
last_x: this.mouse_move_last[0],
last_y: this.mouse_move_last[1],
valid: this.mouse_valid,
click_valid: this.mouse_click_valid,
is_drag: this.is_drag,
speed_average: this.speed_average,
};
if (eventType === 'mousemove' || eventType === 'touchmove') {
//save last pos
this.mouse_move_last[0] = mouse_x;
this.mouse_move_last[1] = mouse_y;
}
}
get_mouse_coordinates_from_event(event){
var mouse_x = event.pageX - this.Base_gui.canvas_offset.x;
var mouse_y = event.pageY - this.Base_gui.canvas_offset.y;
//adapt coords to ZOOM
var global_pos = this.Base_layers.get_world_coords(mouse_x, mouse_y);
mouse_x = global_pos.x;
mouse_y = global_pos.y;
return {
x: mouse_x,
y: mouse_y,
};
}
get_mouse_info(event) {
if(typeof event != "undefined" && typeof mouse.x == "undefined"){
//mouse not set yet - set it now...
this.set_mouse_info(event);
}
return config.mouse;
}
calc_average_mouse_speed(event) {
if (this.is_drag == false)
return null;
//calc average speed
var avg_speed_max = 30;
var avg_speed_changing_power = 2;
var mouse = this.get_mouse_info(event, true);
var dx = Math.abs(mouse.x - mouse.last_x);
var dy = Math.abs(mouse.y - mouse.last_y);
var delta = Math.sqrt(dx * dx + dy * dy);
var mouse_average_speed = this.speed_average;
if (delta > avg_speed_max / 2) {
mouse_average_speed += avg_speed_changing_power;
}
else {
mouse_average_speed -= avg_speed_changing_power;
}
mouse_average_speed = Math.max(0, mouse_average_speed); //min
mouse_average_speed = Math.min(avg_speed_max, mouse_average_speed); //max
return mouse_average_speed;
}
get_params_hash() {
var data = [
this.getParams(),
config.COLOR,
config.ALPHA,
];
return JSON.stringify(data);
}
clone(object) {
return JSON.parse(JSON.stringify(object));
}
/**
* customized mouse cursor
*
* @param {int} x
* @param {int} y
* @param {int} size
* @param {string} type circle, rect
*/
show_mouse_cursor(x, y, size, type) {
//fix coordinates, because of scroll
var start_pos = this.Base_layers.get_world_coords(0, 0);
x = x - start_pos.x;
y = y - start_pos.y;
var element = document.getElementById('mouse');
size = size * config.ZOOM;
x = x * config.ZOOM;
y = y * config.ZOOM;
if (size < 5) {
//too small
element.className = '';
return;
}
element.style.width = size + 'px';
element.style.height = size + 'px';
element.style.left = x - Math.ceil(size / 2) + 'px';
element.style.top = y - Math.ceil(size / 2) + 'px';
//add style
element.className = '';
element.classList.add(type);
}
getParams() {
const params = {};
// Number inputs return the .value if defined as objects.
for (let attributeName in config.TOOL.attributes) {
const attribute = config.TOOL.attributes[attributeName];
if (!isNaN(attribute.value) && attribute.value != null) {
if (typeof attribute.value === 'string') {
params[attributeName] = attribute;
} else {
params[attributeName] = attribute.value;
}
} else {
params[attributeName] = attribute;
}
}
return params;
}
adaptSize(value, type = "width") {
var response;
if (config.layer.width_original == null) {
return value;
}
if (type === "width") {
response = value / (config.layer.width / config.layer.width_original);
}
else {
response = value / (config.layer.height / config.layer.height_original);
}
return response;
}
draw_shape(ctx, x, y, width, height, coords, is_demo) {
if(is_demo !== false) {
ctx.fillStyle = '#aaa';
ctx.strokeStyle = '#555';
ctx.lineWidth = 2;
}
ctx.lineJoin = "round";
ctx.beginPath();
for(var i in coords){
if(coords[i] === null){
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
continue;
}
//coords in 100x100 box
var pos_x = x + coords[i][0] * width / 100;
var pos_y = y + coords[i][1] * height / 100;
if(i == '0')
ctx.moveTo(pos_x, pos_y);
else
ctx.lineTo(pos_x, pos_y);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
default_events(){
var _this = this;
//mouse events
document.addEventListener('mousedown', function (event) {
_this.default_dragStart(event);
});
document.addEventListener('mousemove', function (event) {
_this.default_dragMove(event);
});
document.addEventListener('mouseup', function (event) {
_this.default_dragEnd(event);
});
// collect touch events
document.addEventListener('touchstart', function (event) {
_this.default_dragStart(event);
});
document.addEventListener('touchmove', function (event) {
_this.default_dragMove(event);
});
document.addEventListener('touchend', function (event) {
_this.default_dragEnd(event);
});
}
default_dragStart(event) {
if (config.TOOL.name != this.name)
return;
this.mousedown(event);
}
default_dragMove(event) {
if (config.TOOL.name != this.name)
return;
this.mousemove(event);
}
default_dragEnd(event) {
if (config.TOOL.name != this.name)
return;
this.mouseup(event);
}
shape_mousedown(e) {
var mouse = this.get_mouse_info(e);
if (mouse.click_valid == false)
return;
var mouse_x = mouse.x;
var mouse_y = mouse.y;
//apply snap
var snap_info = this.calc_snap_position(e, mouse_x, mouse_y);
if(snap_info != null){
if(snap_info.x != null) {
mouse_x = snap_info.x;
}
if(snap_info.y != null) {
mouse_y = snap_info.y;
}
}
this.shape_mouse_click.x = mouse_x;
this.shape_mouse_click.y = mouse_y;
//register new object - current layer is not ours or params changed
this.layer = {
type: this.name,
params: this.clone(this.getParams()),
status: 'draft',
render_function: [this.name, 'render'],
x: Math.round(mouse_x),
y: Math.round(mouse_y),
color: null,
is_vector: true
};
app.State.do_action(
new app.Actions.Bundle_action('new_'+this.name+'_layer', 'New '+this.Helper.ucfirst(this.name)+' Layer', [
new app.Actions.Insert_layer_action(this.layer)
])
);
}
shape_mousemove(e) {
var mouse = this.get_mouse_info(e);
var params = this.getParams();
if (mouse.is_drag == false)
return;
if (mouse.click_valid == false) {
return;
}
var mouse_x = Math.round(mouse.x);
var mouse_y = Math.round(mouse.y);
var click_x = Math.round(this.shape_mouse_click.x);
var click_y = Math.round(this.shape_mouse_click.y);
//apply snap
var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id);
if(snap_info != null){
if(snap_info.x != null) {
mouse_x = snap_info.x;
}
if(snap_info.y != null) {
mouse_y = snap_info.y;
}
}
var x = Math.min(mouse_x, click_x);
var y = Math.min(mouse_y, click_y);
var width = Math.abs(mouse_x - click_x);
var height = Math.abs(mouse_y - click_y);
if (e.ctrlKey == true || e.metaKey) {
if (width < height * this.best_ratio) {
width = height * this.best_ratio;
}
else {
height = width / this.best_ratio;
}
if (mouse_x < click_x) {
x = click_x - width;
}
if (mouse_y < click_y) {
y = click_y - height;
}
}
//more data
config.layer.x = x;
config.layer.y = y;
config.layer.width = width;
config.layer.height = height;
this.Base_layers.render();
}
shape_mouseup(e) {
var mouse = this.get_mouse_info(e);
var params = this.getParams();
if (mouse.click_valid == false) {
config.layer.status = null;
return;
}
var mouse_x = Math.round(mouse.x);
var mouse_y = Math.round(mouse.y);
var click_x = Math.round(this.shape_mouse_click.x);
var click_y = Math.round(this.shape_mouse_click.y);
//apply snap
var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id);
if(snap_info != null){
if(snap_info.x != null) {
mouse_x = snap_info.x;
}
if(snap_info.y != null) {
mouse_y = snap_info.y;
}
}
this.snap_line_info = {x: null, y: null};
var x = Math.min(mouse_x, click_x);
var y = Math.min(mouse_y, click_y);
var width = Math.abs(mouse_x - click_x);
var height = Math.abs(mouse_y - click_y);
if (e.ctrlKey == true || e.metaKey) {
if (width < height * this.best_ratio) {
width = height * this.best_ratio;
}
else {
height = width / this.best_ratio;
}
if (mouse_x < click_x) {
x = click_x - width;
}
if (mouse_y < click_y) {
y = click_y - height;
}
}
if (width == 0 && height == 0) {
//same coordinates - cancel
app.State.scrap_last_action();
return;
}
//more data
app.State.do_action(
new app.Actions.Update_layer_action(config.layer.id, {
x,
y,
width,
height,
status: null
}),
{ merge_with_history: 'new_'+this.name+'_layer' }
);
}
render_overlay_parent(ctx){
//x
if(this.snap_line_info.x !== null) {
this.Helper.draw_special_line(
ctx,
this.snap_line_info.x.start_x,
this.snap_line_info.x.start_y,
this.snap_line_info.x.end_x,
this.snap_line_info.x.end_y
);
}
//y
if(this.snap_line_info.y !== null) {
this.Helper.draw_special_line(
ctx,
this.snap_line_info.y.start_x,
this.snap_line_info.y.start_y,
this.snap_line_info.y.end_x,
this.snap_line_info.y.end_y
);
}
}
get_snap_positions(exclude_id) {
var snap_positions = {
x: [
0,
config.WIDTH/2,
config.WIDTH,
],
y: [
0,
config.HEIGHT/2,
config.HEIGHT,
],
};
if(config.guides_enabled == true){
//use guides
for(var i in config.guides){
var guide = config.guides[i];
if(guide.y === null)
snap_positions.x.push(guide.x);
else
snap_positions.y.push(guide.y);
}
}
for(var i in config.layers){
if(exclude_id != null && exclude_id == config.layers[i].id){
continue;
}
if(config.layers[i].visible == false
|| config.layers[i].x === null || config.layers[i].y === null
|| config.layers[i].width === null || config.layers[i].height === null){
continue;
}
//x
var x = config.layers[i].x;
if(x > 0 && x < config.WIDTH)
snap_positions.x.push(x);
var x = config.layers[i].x + config.layers[i].width/2;
if(x > 0 && x < config.WIDTH)
snap_positions.x.push(x);
var x = config.layers[i].x + config.layers[i].width;
if(x > 0 && x < config.WIDTH)
snap_positions.x.push(x);
//y
var y = config.layers[i].y;
if(y > 0 && y < config.HEIGHT)
snap_positions.y.push(y);
var y = config.layers[i].y + config.layers[i].height/2;
if(y > 0 && y < config.HEIGHT)
snap_positions.y.push(y);
var y = config.layers[i].y + config.layers[i].height;
if(y > 0 && y < config.HEIGHT)
snap_positions.y.push(y);
}
return snap_positions;
}
/**
* calculates snap coordinates by current mouse position.
*
* @param event
* @param pos_x
* @param pos_y
* @param exclude_id
* @returns object|null
*/
calc_snap_position(event, pos_x, pos_y, exclude_id) {
var snap_position = { x: null, y: null };
var params = this.getParams();
if(config.SNAP === false || event.shiftKey == true || (event.ctrlKey == true || event.metaKey == true)){
this.snap_line_info = {x: null, y: null};
return null;
}
//settings
var sensitivity = 0.01;
var max_distance = (config.WIDTH + config.HEIGHT) / 2 * sensitivity / config.ZOOM;
//collect snap positions
if(typeof exclude_id != "undefined")
var snap_positions = this.get_snap_positions(exclude_id);
else
var snap_positions = this.get_snap_positions();
//find closest snap positions
var min_value = {
x: null,
y: null,
};
var min_distance = {
x: null,
y: null,
};
//x
for(var i in snap_positions.x){
var distance = Math.abs(pos_x - snap_positions.x[i]);
if(distance < max_distance && (distance < min_distance.x || min_distance.x === null)){
min_distance.x = distance;
min_value.x = snap_positions.x[i];
}
}
//y
for(var i in snap_positions.y){
var distance = Math.abs(pos_y - snap_positions.y[i]);
if(distance < max_distance && (distance < min_distance.y || min_distance.y === null)){
min_distance.y = distance;
min_value.y = snap_positions.y[i];
}
}
//apply snap
var success = false;
//x
if(min_value.x != null) {
snap_position.x = Math.round(min_value.x);
success = true;
this.snap_line_info.x = {
start_x: min_value.x,
start_y: 0,
end_x: min_value.x,
end_y: config.HEIGHT
};
}
else{
this.snap_line_info.x = null;
}
//y
if(min_value.y != null) {
snap_position.y = Math.round(min_value.y);
success = true;
this.snap_line_info.y = {
start_x: 0,
start_y: min_value.y,
end_x: config.WIDTH,
end_y: min_value.y,
};
}
else{
this.snap_line_info.y = null;
}
if(success) {
return snap_position;
}
return null;
}
}
export default Base_tools_class;
@@ -0,0 +1,184 @@
import Helper_class from './../../libs/helpers.js';
import Dialog_class from './../../libs/popup.js';
import GUI_colors_class from './../gui/gui-colors.js';
const Helper = new Helper_class();
/**
* This input opens a custom color picker dialog that is more tightly integrated with the application (swatch selection, etc).
* It can also handle alpha values, whereas native color input can't.
*/
(function ($) {
const template = `
<div class="ui_color_input" tabindex="-1">
<input type="color">
<div class="alpha_overlay"></div>
</div>
`;
const on_focus_color_input = (event) => {
const $el = $(event.target.closest('.ui_color_input'));
$el.trigger('focus');
};
const on_blur_color_input = (event) => {
const $el = $(event.target.closest('.ui_color_input'));
$el.trigger('blur');
};
const on_click_color_input = (event) => {
event.preventDefault();
const $el = $(event.target.closest('.ui_color_input'));
const { value } = $el.data();
const POP = new Dialog_class();
let colorsDialog = new GUI_colors_class();
var settings = {
title: 'Color Picker',
on_finish() {
set_value($el, colorsDialog.COLOR + (colorsDialog.ALPHA < 255 ? colorsDialog.ALPHA.toString(16).padStart(2, '0') : ''));
$el.trigger('input');
$el.trigger('change');
colorsDialog = null;
},
params: [
{
function() {
var html = '<div id="dialog_color_picker"></div>';
return html;
}
}
],
};
let colorValue;
let alpha = 255;
if (/^\#[0-9A-F]{8}$/gi.test(value)) {
// Hex with alpha
colorValue = value.slice(0, 7);
alpha = parseInt(value.slice(7, 9), 16);
} else if (/^\#[0-9A-F]{6}$/gi.test(value)) {
// Hex without alpha
colorValue = value;
} else {
colorValue = '#000000';
}
POP.show(settings);
colorsDialog.render_main_colors('dialog');
colorsDialog.set_color({ hex: colorValue, a: alpha });
};
const set_value = ($el, value) => {
const trimmedValue = (value + '').trim();
let colorValue;
let opacity = 0;
if (/^\#[0-9A-F]{8}$/gi.test(trimmedValue)) {
// Hex with alpha
colorValue = trimmedValue.slice(0, 7);
opacity = 1 - (parseInt(value.slice(7, 9), 16) * (1 / 255));
} else if (/^\#[0-9A-F]{6}$/gi.test(trimmedValue)) {
// Hex without alpha
colorValue = trimmedValue;
} else {
return;
}
const { input, overlay } = $el.data();
overlay.style.opacity = opacity;
input.value = colorValue;
$el.data('value', trimmedValue);
};
const set_disabled = ($el, disabled) => {
const { input } = $el.data();
if (disabled) {
input.setAttribute('disabled', 'disabled');
} else {
input.removeAttribute('disabled');
}
$el.data('disabled', disabled);
};
$.fn.uiColorInput = function(behavior, ...args) {
let returnValues = [];
for (let i = 0; i < this.length; i++) {
let el = this[i];
// Constructor
if (Object.prototype.toString.call(behavior) !== '[object String]') {
const definition = behavior || {};
const classList = el.className;
const id = definition.id != null ? definition.id : el.getAttribute('id');
const inputId = definition.inputId || '';
const disabled = definition.disabled != null ? definition.disabled : el.hasAttribute('disabled') ? true : false;
const value = definition.value != null ? definition.value : el.value || 0;
const ariaLabeledBy = el.getAttribute('aria-labelledby');
let $el;
if (el.parentNode) {
$(el).after(template);
const oldEl = el;
el = el.nextElementSibling;
$(oldEl).remove();
} else {
const orphanedParent = document.createElement('div');
orphanedParent.innerHTML = template;
el = orphanedParent.firstElementChild;
}
this[i] = el;
$el = $(el);
const input = $el.find('input[type="color"]')[0];
const overlay = $el.find('.alpha_overlay')[0];
if (classList) {
el.classList.add(classList);
}
if (id) {
el.setAttribute('id', id);
}
if (inputId) {
input.setAttribute('id', inputId);
}
if (ariaLabeledBy) {
input.setAttribute('aria-labelledby', ariaLabeledBy);
}
$el.data({
id,
input,
overlay,
value
});
$(input)
.on('click', on_click_color_input)
.on('focus', on_focus_color_input)
.on('blur', on_blur_color_input)
set_value($el, value);
set_disabled($el, disabled);
}
// Behaviors
else if (behavior === 'set_value') {
const newValue = args[0];
const $el = $(el);
if ($el.data('value') !== newValue) {
set_value($(el), newValue);
}
}
else if (behavior === 'get_value') {
returnValues.push($(el).data('value'));
}
else if (behavior === 'get_id') {
returnValues.push($(el).data('id'));
}
}
if (returnValues.length > 0) {
return returnValues.length === 1 ? returnValues[0] : returnValues;
} else {
return this;
}
}
})(jQuery);
@@ -0,0 +1,213 @@
import Helper_class from './../../libs/helpers.js';
var Helper = new Helper_class();
(function ($) {
const template = `
<div class="ui_color_picker_gradient">
<div class="secondary_pick" tabindex="0" role="figure" aria-label="Saturation vs value selection. Use left/right arrow keys to control saturation. Use up/down arrow keys to control value.">
<div class="saturation_gradient"></div>
<div class="value_gradient"></div>
<div class="handle"></div>
</div>
<div class="primary_pick">
<input aria-label="Hue" type="range" min="0" max="360" step="1" class="color_picker_thin" />
</div>
</div>
`;
const on_key_down_secondary_pick = (event) => {
const $el = $(event.target.closest('.ui_color_picker_gradient'));
const { hsv } = $el.data();
const key = event.key;
if (['Left', 'ArrowLeft'].includes(key)) {
event.preventDefault();
set_hsv($el, {
h: hsv.h,
s: hsv.s - 1/100,
v: hsv.v
});
$el.trigger('input');
}
else if (['Right', 'ArrowRight'].includes(key)) {
event.preventDefault();
set_hsv($el, {
h: hsv.h,
s: hsv.s + 1/100,
v: hsv.v
});
$el.trigger('input');
}
else if (['Up', 'ArrowUp'].includes(key)) {
event.preventDefault();
set_hsv($el, {
h: hsv.h,
s: hsv.s,
v: hsv.v + 1/100
});
$el.trigger('input');
}
else if (['Down', 'ArrowDown'].includes(key)) {
event.preventDefault();
set_hsv($el, {
h: hsv.h,
s: hsv.s,
v: hsv.v - 1/100
});
$el.trigger('input');
}
};
const on_mouse_down_secondary_pick = (event) => {
event.preventDefault();
const $el = $(event.target.closest('.ui_color_picker_gradient'));
const { secondaryPick, secondaryPickHandle, hsv } = $el.data();
const clientX = event.touches && event.touches.length > 0 ? event.touches[0].clientX : event.clientX;
const clientY = event.touches && event.touches.length > 0 ? event.touches[0].clientY : event.clientY;
const mouseDownSecondaryPickRect = secondaryPick.getBoundingClientRect();
const xRatio = (clientX - mouseDownSecondaryPickRect.left) / (mouseDownSecondaryPickRect.right - mouseDownSecondaryPickRect.left);
const yRatio = (clientY - mouseDownSecondaryPickRect.top) / (mouseDownSecondaryPickRect.bottom - mouseDownSecondaryPickRect.top);
set_hsv($el, {
h: hsv.h,
s: xRatio,
v: 1 - yRatio
});
$el.trigger('input');
$el.data({
mouseDownSecondaryPickRect,
mouseMoveWindowHandler: generate_on_mouse_move_window($el),
mouseUpWindowHandler: generate_on_mouse_up_window($el)
});
const $window = $(window);
$window.on('mousemove touchmove', $el.data('mouseMoveWindowHandler'));
$window.on('mouseup touchend', $el.data('mouseUpWindowHandler'));
};
const on_touch_move_secondary_pick = (event) => {
event.preventDefault();
};
const generate_on_mouse_move_window = ($el) => {
return (event) => {
const { hsv, mouseDownSecondaryPickRect } = $el.data();
const clientX = event.touches && event.touches.length > 0 ? event.touches[0].clientX : event.clientX;
const clientY = event.touches && event.touches.length > 0 ? event.touches[0].clientY : event.clientY;
const xRatio = (clientX - mouseDownSecondaryPickRect.left) / (mouseDownSecondaryPickRect.right - mouseDownSecondaryPickRect.left);
const yRatio = (clientY - mouseDownSecondaryPickRect.top) / (mouseDownSecondaryPickRect.bottom - mouseDownSecondaryPickRect.top);
set_hsv($el, {
h: hsv.h,
s: xRatio,
v: 1 - yRatio
});
$el.trigger('input');
};
};
const generate_on_mouse_up_window = ($el) => {
return (event) => {
const $window = $(window);
$window.off('mousemove touchmove', $el.data('mouseMoveWindowHandler'));
$window.off('mouseup touchend', $el.data('mouseUpWindowHandler'));
};
};
// All hsv values range from 0 to 1.
const set_hsv = ($el, hsv) => {
const { secondaryPick, secondaryPickHandle, primaryRange } = $el.data();
hsv.h = Math.max(0, Math.min(1, hsv.h));
hsv.s = Math.max(0, Math.min(1, hsv.s));
hsv.v = Math.max(0, Math.min(1, hsv.v));
$el.data('hsv', hsv);
$(primaryRange).uiRange('set_value', (1 - hsv.h) * 360);
secondaryPick.style.background = Helper.hsvToHex(hsv.h, 1, 1);
secondaryPickHandle.style.left = ((hsv.s) * 100) + '%';
secondaryPickHandle.style.top = ((1 - hsv.v) * 100) + '%';
};
$.fn.uiColorPickerGradient = function(behavior, ...args) {
let returnValues = [];
for (let i = 0; i < this.length; i++) {
let el = this[i];
// Constructor
if (Object.prototype.toString.call(behavior) !== '[object String]') {
const definition = behavior || {};
const id = definition.id != null ? definition.id : el.getAttribute('id');
const label = definition.label != null ? definition.label : el.getAttribute('aria-label');
const hsv = definition.hsv || { h: 0, s: 0, v: 0 };
$(el).after(template);
const oldEl = el;
el = el.nextElementSibling;
$(oldEl).remove();
this[i] = el;
if (id) {
el.setAttribute('id', id);
}
if (label) {
el.setAttribute('aria-label', label);
}
const $el = $(el);
const $primaryRange = $($el.find('.primary_pick input').get(0));
$primaryRange
.uiRange({ vertical: true })
.uiRange('set_background', 'linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)')
.on('input', () => {
const { hsv } = $el.data();
set_hsv($el, {
h: 1 - ($primaryRange.uiRange('get_value') / 360),
s: hsv.s,
v: hsv.v
});
$el.trigger('input');
});
$el.find('> input').uiRange();
const secondaryPick = $el.find('.secondary_pick')[0];
$el.data({
primaryRange: $primaryRange[0],
secondaryPick,
secondaryPickHandle: $el.find('.secondary_pick .handle')[0],
hsv
});
set_hsv($el, hsv);
$(secondaryPick).on('keydown', on_key_down_secondary_pick);
$(secondaryPick).on('mousedown touchstart', on_mouse_down_secondary_pick);
$(secondaryPick).on('touchmove', on_touch_move_secondary_pick);
}
// Behaviors
else if (behavior === 'set_hsv') {
const $el = $(el);
const hsv = $el.data('hsv');
const newHsv = args[0];
if (newHsv && (hsv.h !== newHsv.h || hsv.s !== newHsv.s || hsv.v !== newHsv.v)) {
set_hsv($(el), newHsv);
}
}
else if (behavior === 'get_hsv') {
const hsv = $(el).data('hsv');
returnValues.push(JSON.parse(JSON.stringify(hsv)));
}
}
if (returnValues.length > 0) {
return returnValues.length === 1 ? returnValues[0] : returnValues;
} else {
return this;
}
};
})(jQuery);
+6
View File
@@ -0,0 +1,6 @@
import './color-input.js';
import './color-picker-gradient.js';
import './number-input.js';
import './range.js';
import './swatches.js';
@@ -0,0 +1,306 @@
import Helper_class from './../../libs/helpers.js';
var Helper = new Helper_class();
/**
* The purpose of using this class vs a native input[type="number"] is for custom styling and
* to allow for gestures on mobile that makes it easier to use with a thumb on a touch screen (future implementation)
*/
(function ($) {
const template = `
<div class="ui_number_input">
<input type="number">
<button class="increase_number" tabindex="-1"><span class="sr_only">Increase</span></button>
<button class="decrease_number" tabindex="-1"><span class="sr_only">Decrease</span></button>
</div>
`;
const on_focus_number_input = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
$el.trigger('focus', event);
};
const on_blur_number_input = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
$el.trigger('blur', event);
};
const on_input_number_input = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const value = $el.data('input').value;
if (value != '') {
set_value($el, $el.data('input').value);
}
$el.trigger('input', event);
};
const on_change_number_input = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { input, min } = $el.data();
let value = input.value;
if (value === '') {
value = 0;
}
set_value($el, value);
$el.trigger('change', event);
};
const on_wheel_number_input = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { value, step, disabled } = $el.data();
event.preventDefault();
const delta = (event.originalEvent.deltaY > 0 ? -1 : (event.originalEvent.deltaY < 0 ? 1 : 0));
if (!disabled && delta !== 0) {
set_value($el, (isNaN(value) ? 0 : value) + (step * delta)); // Intentionally not using get_step_amount
$el.trigger('input');
}
}
const on_touch_start_increase_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { value, buttonRepeatTimeout, buttonRepeatInterval, disabled } = $el.data();
if (!disabled) {
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
set_value($el, (isNaN(value) ? 0 : value) + get_step_amount($el, true));
$el.trigger('input');
}
};
const on_mouse_down_increase_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { value, buttonRepeatTimeout, buttonRepeatInterval, disabled } = $el.data();
if (!disabled) {
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
set_value($el, (isNaN(value) ? 0 : value) + get_step_amount($el, true));
$el.trigger('input');
$el.data('buttonRepeatTimeout', setTimeout(() => {
$el.data('buttonRepeatInterval', setInterval(() => {
const { value } = $el.data();
set_value($el, value + get_step_amount($el, true));
$el.trigger('input');
}, 50));
}, 400));
}
};
const on_mouse_up_increase_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { buttonRepeatTimeout, buttonRepeatInterval } = $el.data();
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
};
const on_touch_start_decrease_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { value, buttonRepeatTimeout, buttonRepeatInterval, disabled } = $el.data();
if (!disabled) {
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
set_value($el, (isNaN(value) ? 0 : value) - get_step_amount($el, false));
$el.trigger('input');
}
};
const on_mouse_down_decrease_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { value, buttonRepeatTimeout, buttonRepeatInterval, disabled } = $el.data();
if (!disabled) {
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
set_value($el, (isNaN(value) ? 0 : value) - get_step_amount($el, false));
$el.trigger('input');
$el.data('buttonRepeatTimeout', setTimeout(() => {
$el.data('buttonRepeatInterval', setInterval(() => {
const { value } = $el.data();
set_value($el, value - get_step_amount($el, false));
$el.trigger('input');
}, 50));
}, 400));
}
};
const on_mouse_up_decrease_button = (event) => {
const $el = $(event.target.closest('.ui_number_input'));
const { buttonRepeatTimeout, buttonRepeatInterval } = $el.data();
clearTimeout(buttonRepeatTimeout);
clearInterval(buttonRepeatInterval);
};
const set_value = ($el, value) => {
const { min, max, step, stepDecimalPlaces, input } = $el.data();
if (typeof value === 'string') {
value = parseFloat(value);
}
if (!isNaN(value)) {
value = parseFloat((step * Math.round(value / step)).toFixed(stepDecimalPlaces));
value = Math.max(min, Math.min(max, value));
if (value + '.' !== input.value) {
input.value = value;
}
} else {
value = parseFloat(null);
input.value = '';
}
$el.data('value', value);
};
const set_disabled = ($el, disabled) => {
const { input } = $el.data();
if (disabled) {
input.setAttribute('disabled', 'disabled');
} else {
input.removeAttribute('disabled');
}
$el.data('disabled', disabled);
};
const get_step_amount = ($el, increasing) => {
const { value, step, exponentialStepButtons } = $el.data();
if (exponentialStepButtons) {
let amount = step;
let absValue = Math.abs((isNaN(value) ? 0 : value));
if (absValue >= (increasing ? 500 : 501))
amount = 100;
else if (absValue >= (increasing ? 100 : 101))
amount = 50;
else if (absValue >= (increasing ? 10 : 11))
amount = 10;
else if (absValue >= (increasing ? 5 : 6))
amount = 5;
else
amount = 1;
return amount;
} else {
return step;
}
};
$.fn.uiNumberInput = function(behavior, ...args) {
let returnValues = [];
for (let i = 0; i < this.length; i++) {
let el = this[i];
// Constructor
if (Object.prototype.toString.call(behavior) !== '[object String]') {
const definition = behavior || {};
const classList = el.className;
const id = definition.id != null ? definition.id : el.getAttribute('id');
const min = definition.min != null ? definition.min : parseFloat(el.getAttribute('min')) || null;
const max = definition.max != null ? definition.max : parseFloat(el.getAttribute('max')) || null;
const step = definition.step != null ? definition.step : el.hasAttribute('step') ? parseFloat(el.getAttribute('step')) : 1;
const exponentialStepButtons = !!definition.exponentialStepButtons;
const disabled = definition.disabled != null ? definition.disabled : el.hasAttribute('disabled') ? true : false;
const value = definition.value != null ? definition.value : parseFloat(el.value) || 0;
const ariaLabeledBy = el.getAttribute('aria-labelledby');
let $el;
if (el.parentNode) {
$(el).after(template);
const oldEl = el;
el = el.nextElementSibling;
$(oldEl).remove();
} else {
const orphanedParent = document.createElement('div');
orphanedParent.innerHTML = template;
el = orphanedParent.firstElementChild;
}
this[i] = el;
$el = $(el);
const input = $el.find('input[type="number"]')[0];
const increaseButton = $el.find('.increase_number')[0];
const decreaseButton = $el.find('.decrease_number')[0];
if (classList) {
el.classList.add(classList);
}
if (id) {
el.setAttribute('id', id);
}
if (ariaLabeledBy) {
input.setAttribute('aria-labelledby', ariaLabeledBy);
}
if (min != null) {
input.setAttribute('min', min);
}
if (max != null) {
input.setAttribute('max', max);
}
if (Math.floor(step) === step) {
input.setAttribute('step', step);
} else {
input.setAttribute('step', 'any');
}
let stepDecimalPlaces = 0;
if ((step % 1) != 0)
stepDecimalPlaces = step.toString().split(".")[1].length;
$el.data({
id,
input,
increaseButton,
decreaseButton,
buttonRepeatTimeout: undefined,
buttonRepeatInterval: undefined,
value,
min,
max,
step,
stepDecimalPlaces,
exponentialStepButtons
});
$(input)
.on('focus', on_focus_number_input)
.on('blur', on_blur_number_input)
.on('input', on_input_number_input)
.on('change', on_change_number_input)
.on('wheel', on_wheel_number_input);
$(increaseButton)
.on('touchstart', on_touch_start_increase_button)
.on('mousedown', on_mouse_down_increase_button)
.on('mouseup mouseleave touchend', on_mouse_up_increase_button);
$(decreaseButton)
.on('touchstart', on_touch_start_decrease_button)
.on('mousedown', on_mouse_down_decrease_button)
.on('mouseup mouseleave', on_mouse_up_decrease_button);
set_value($el, value);
set_disabled($el, disabled);
}
// Behaviors
else if (behavior === 'set_value') {
const newValue = parseFloat(args[0]);
const $el = $(el);
if ($el.data('value') !== newValue) {
set_value($(el), newValue);
}
}
else if (behavior === 'get_value') {
returnValues.push($(el).data('value'));
}
else if (behavior === 'get_id') {
returnValues.push($(el).data('id'));
}
else if (behavior === 'set_disabled') {
const newValue = !!args[0];
set_disabled($(el), newValue);
}
else if (behavior === 'get_disabled') {
returnValues.push($(el).data('disabled'));
}
}
if (returnValues.length > 0) {
return returnValues.length === 1 ? returnValues[0] : returnValues;
} else {
return this;
}
};
})(jQuery);
+234
View File
@@ -0,0 +1,234 @@
(function ($) {
const template = `
<div class="ui_range" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="1" aria-valuenow="0">
<div class="padded_track"></div>
<div class="bar">
<div class="handle"></div>
</div>
</div>
`;
const on_keydown_range = (event) => {
const $el = $(event.target.closest('.ui_range'));
const key = event.key;
const { value, step, min, max } = $el.data();
if (['Left', 'ArrowLeft', 'Down', 'ArrowDown'].includes(key)) {
event.preventDefault();
set_value($el, value - step);
$el.trigger('input');
}
else if (['Right', 'ArrowRight', 'Up', 'ArrowUp'].includes(key)) {
event.preventDefault();
set_value($el, value + step);
$el.trigger('input');
}
else if (['PageUp'].includes(key)) {
event.preventDefault();
set_value($el, value + (step * 10));
$el.trigger('input');
}
else if (['PageDown'].includes(key)) {
event.preventDefault();
set_value($el, value - (step * 10));
$el.trigger('input');
}
else if (['Home'].includes(key)) {
event.preventDefault();
set_value($el, min);
$el.trigger('input');
}
else if (['End'].includes(key)) {
event.preventDefault();
set_value($el, max);
$el.trigger('input');
}
};
const on_wheel_range = (event) => {
const $el = $(event.target.closest('.ui_range'));
if (document.activeElement === $el[0]) {
const { value, step } = $el.data();
if (event.originalEvent.deltaY < 0) {
event.preventDefault();
set_value($el, value + step);
$el.trigger('input');
}
else if (event.originalEvent.deltaY > 0) {
event.preventDefault();
set_value($el, value - step);
$el.trigger('input');
}
}
};
const on_mouse_down_range = (event) => {
event.preventDefault();
const target = event.touches && event.touches.length > 0 ? event.touches[0].target : event.target;
const $el = $(target.closest('.ui_range'));
const { handle, paddedTrack, value, min, max, vertical } = $el.data();
const mouseDownClientX = event.touches && event.touches.length > 0 ? event.touches[0].clientX : event.clientX;
const mouseDownClientY = event.touches && event.touches.length > 0 ? event.touches[0].clientY : event.clientY;
const mouseDownPaddedTrackRect = paddedTrack.getBoundingClientRect();
let mouseDownValue = value;
if (target !== handle) {
let range, valueInRange;
if (vertical) {
range = mouseDownPaddedTrackRect.top - mouseDownPaddedTrackRect.bottom;
valueInRange = mouseDownClientY - mouseDownPaddedTrackRect.bottom;
} else {
range = mouseDownPaddedTrackRect.right - mouseDownPaddedTrackRect.left;
valueInRange = mouseDownClientX - mouseDownPaddedTrackRect.left;
}
const ratio = Math.max(0, Math.min(1, valueInRange / range));
mouseDownValue = (max - min) * ratio;
set_value($el, mouseDownValue);
$el.trigger('input');
}
$el.data({
mouseDownValue,
mouseDownClientX,
mouseDownClientY,
mouseDownPaddedTrackRect,
mouseMoveWindowHandler: generate_on_mouse_move_window($el),
mouseUpWindowHandler: generate_on_mouse_up_window($el)
});
$el.addClass('active');
const $window = $(window);
$window.on('mousemove touchmove', $el.data('mouseMoveWindowHandler'));
$window.on('mouseup touchend', $el.data('mouseUpWindowHandler'));
$el[0].focus();
};
const on_touch_move_range = (event) => {
event.preventDefault();
};
const generate_on_mouse_move_window = ($el) => {
return (event) => {
event.preventDefault();
event.stopPropagation();
const { mouseDownValue, min, max, vertical, mouseDownClientX, mouseDownClientY, mouseDownPaddedTrackRect } = $el.data();
let range, offset, startValue;
if (vertical) {
const clientY = event.touches && event.touches.length > 0 ? event.touches[0].clientY : event.clientY;
range = mouseDownPaddedTrackRect.top - mouseDownPaddedTrackRect.bottom;
const mouseDownValueInPixelRange = ((mouseDownValue - min) / (max - min)) * range;
startValue = mouseDownClientY - mouseDownPaddedTrackRect.bottom;
offset = clientY - mouseDownClientY + (mouseDownValueInPixelRange - startValue);
} else {
const clientX = event.touches && event.touches.length > 0 ? event.touches[0].clientX : event.clientX;
range = mouseDownPaddedTrackRect.right - mouseDownPaddedTrackRect.left;
const mouseDownValueInPixelRange = ((mouseDownValue - min) / (max - min)) * range;
startValue = mouseDownClientX - mouseDownPaddedTrackRect.left;
offset = clientX - mouseDownClientX + (mouseDownValueInPixelRange - startValue);
}
const ratio = Math.max(0, Math.min(1, (startValue + offset) / range));
const value = (max - min) * ratio;
set_value($el, value);
$el.trigger('input');
};
};
const generate_on_mouse_up_window = ($el) => {
return (event) => {
const $window = $(window);
$el.removeClass('active');
$window.off('mousemove touchmove', $el.data('mouseMoveWindowHandler'));
$window.off('mouseup touchend', $el.data('mouseUpWindowHandler'));
};
};
const set_value = ($el, value) => {
const { bar, min, max, step, vertical } = $el.data();
value = step * Math.round(value / step);
value = Math.max(min, Math.min(max, value));
$el.data('value', value);
$el.attr('aria-valuemin', min);
$el.attr('aria-valuemax', max);
$el.attr('aria-valuenow', value);
if (vertical) {
bar.style.height = (((value - min) / (max - min)) * 100) + '%';
} else {
bar.style.width = (((value - min) / (max - min)) * 100) + '%';
}
};
$.fn.uiRange = function(behavior, ...args) {
let returnValues = [];
for (let i = 0; i < this.length; i++) {
let el = this[i];
// Constructor
if (Object.prototype.toString.call(behavior) !== '[object String]') {
const definition = behavior || {};
const classList = el.className;
const id = definition.id != null ? definition.id : el.getAttribute('id');
const value = definition.value != null ? definition.value : parseFloat(el.value) || 0;
const min = definition.min != null ? definition.min : parseFloat(el.getAttribute('min')) || 0;
const max = definition.max != null ? definition.max : parseFloat(el.getAttribute('max')) || 0;
const step = definition.step != null ? definition.step : el.hasAttribute('step') ? parseFloat(el.getAttribute('step')) : 1;
const vertical = !!definition.vertical;
$(el).after(template);
const oldEl = el;
el = el.nextElementSibling;
$(oldEl).remove();
this[i] = el;
const $el = $(el);
if (classList) {
el.classList.add(classList);
}
if (vertical) {
el.classList.add('vertical');
}
if (id) {
el.setAttribute('id', id);
}
$el.data({
paddedTrack: $('.padded_track', el).get(0),
bar: $('.bar', el).get(0),
handle: $('.handle', el).get(0),
vertical,
value,
min,
max,
step
});
set_value($el, value);
$el
.on('mousedown touchstart', on_mouse_down_range)
.on('touchmove', on_touch_move_range)
.on('keydown', on_keydown_range)
.on('wheel', on_wheel_range);
}
// Behaviors
else if (behavior === 'set_background') {
const backgroundStyle = args[0];
$(el).data('paddedTrack').style.background = backgroundStyle;
}
else if (behavior === 'set_value') {
const newValue = parseFloat(args[0]);
const $el = $(el);
if ($el.data('value') !== newValue) {
set_value($(el), newValue);
}
}
else if (behavior === 'get_value') {
returnValues.push($(el).data('value'));
}
}
if (returnValues.length > 0) {
return returnValues.length === 1 ? returnValues[0] : returnValues;
} else {
return this;
}
};
})(jQuery);
+170
View File
@@ -0,0 +1,170 @@
(function ($) {
const template = `
<div class="ui_swatches">
<div class="swatch_group" tabindex="0">
</div>
</div>
`;
const on_key_down_swatches = (event) => {
const $el = $(event.target.closest('.ui_swatches'));
const key = event.key;
const { rows, count, selectedIndex } = $el.data();
if (['Left', 'ArrowLeft'].includes(key)) {
event.preventDefault();
set_selected_index($el, selectedIndex - 1);
$el.trigger('input');
}
else if (['Right', 'ArrowRight'].includes(key)) {
event.preventDefault();
set_selected_index($el, selectedIndex + 1);
$el.trigger('input');
}
else if (['Up', 'ArrowUp'].includes(key)) {
event.preventDefault();
set_selected_index($el, selectedIndex - Math.floor(count / rows));
$el.trigger('input');
}
else if (['Down', 'ArrowDown'].includes(key)) {
event.preventDefault();
set_selected_index($el, selectedIndex + Math.floor(count / rows));
$el.trigger('input');
}
};
const on_click_swatches = (event) => {
const target = event.target;
const $el = $(target.closest('.ui_swatches'));
if (target.classList.contains('swatch')) {
const { swatches } = $el.data();
set_selected_index($el, swatches.indexOf(target));
$el.trigger('input');
}
};
const set_selected_index = ($el, index) => {
const { readonly, swatches } = $el.data();
if (swatches[index]) {
$el.data('selectedIndex', index);
if (!readonly) {
$el.find('.active').removeClass('active');
$(swatches[index]).addClass('active');
}
}
};
const set_selected_hex = ($el, hex) => {
const { selectedIndex, swatches } = $el.data();
if (/^\#[0-9A-F]{6}$/gi.test(hex)) {
const swatch = swatches[selectedIndex];
$(swatch)
.data('hex', hex)
.css('background-color', hex);
}
};
const set_all_hex = ($el, hexArray) => {
hexArray = hexArray || [];
const { swatches } = $el.data();
for (let i = 0; i < swatches.length; i++) {
if (hexArray[i]) {
const hex = hexArray[i];
if (/^\#[0-9A-F]{6}$/gi.test(hex)) {
$(swatches[i])
.data('hex', hex)
.css('background-color', hex);
}
} else {
break;
}
}
}
$.fn.uiSwatches = function(behavior, ...args) {
let returnValues = [];
for (let i = 0; i < this.length; i++) {
let el = this[i];
// Constructor
if (Object.prototype.toString.call(behavior) !== '[object String]') {
const definition = behavior || {};
const id = definition.id != null ? definition.id : el.getAttribute('id');
const cols = definition.cols;
const rows = definition.rows || 1;
const count = definition.count || 10;
const readonly = definition.readonly || false;
const selectedIndex = definition.selectedIndex != null ? definition.selectedIndex : 0;
$(el).after(template);
const oldEl = el;
el = el.nextElementSibling;
$(oldEl).remove();
this[i] = el;
const $el = $(el);
const swatchGroup = $el.find('.swatch_group')[0];
if (id) {
el.setAttribute('id', id);
}
if (cols) {
swatchGroup.classList.add('cols_' + cols);
}
swatchGroup.classList.add('rows_' + rows);
const swatches = [];
for (let i = 0; i < count; i++) {
const swatch = document.createElement('div');
swatch.classList.add('swatch');
$(swatch).data('hex', '#ffffff');
swatches.push(swatch);
swatchGroup.appendChild(swatch);
if (i === selectedIndex && !readonly) {
swatch.classList.add('active');
}
}
$el.data({
selectedIndex,
swatchGroup,
swatches,
count,
cols,
rows,
readonly
});
$el
.on('click', on_click_swatches)
.on('keydown', on_key_down_swatches);
}
// Behaviors
else if (behavior === 'set_selected_hex') {
const newValue = args[0] + '';
set_selected_hex($(el), newValue);
}
else if (behavior === 'get_selected_hex') {
const { selectedIndex, swatches } = $(el).data();
returnValues.push($(swatches[selectedIndex]).data('hex'));
}
else if (behavior === 'set_all_hex') {
set_all_hex($(el), args[0]);
}
else if (behavior === 'get_all_hex') {
const { swatches } = $(el).data();
for (let swatch of swatches) {
returnValues.push($(swatch).data('hex'));
}
}
}
if (returnValues.length > 0) {
return returnValues.length === 1 ? returnValues[0] : returnValues;
} else {
return this;
}
};
})(jQuery);
+587
View File
@@ -0,0 +1,587 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../../config.js';
import Helper_class from './../../libs/helpers.js';
import Tools_translate_class from './../../modules/tools/translate.js';
const Helper = new Helper_class();
const sidebarTemplate = `
<div class="ui_flex_group justify_content_space_between stacked">
<div id="selected_color_sample" class="ui_color_sample" title="Current Color Preview"></div>
<div class="ui_button_group">
<button id="toggle_color_picker_section_button" aria-pressed="true" class="ui_icon_button trn" title="Toggle Color Picker">
<span class="sr_only">Toggle Color Picker</span>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" opacity="0" />
<path d="M19.54 5.08A10.61 10.61 0 0 0 11.91 2a10 10 0 0 0-.05 20 2.58 2.58 0 0 0 2.53-1.89 2.52 2.52 0 0 0-.57-2.28.5.5 0 0 1 .37-.83h1.65A6.15 6.15 0 0 0 22 11.33a8.48 8.48 0 0 0-2.46-6.25zM15.88 15h-1.65a2.49 2.49 0 0 0-1.87 4.15.49.49 0 0 1 .12.49c-.05.21-.28.34-.59.36a8 8 0 0 1-7.82-9.11A8.1 8.1 0 0 1 11.92 4H12a8.47 8.47 0 0 1 6.1 2.48 6.5 6.5 0 0 1 1.9 4.77A4.17 4.17 0 0 1 15.88 15z" />
<circle cx="12" cy="6.5" r="1.5" />
<path d="M15.25 7.2a1.5 1.5 0 1 0 2.05.55 1.5 1.5 0 0 0-2.05-.55z" />
<path d="M8.75 7.2a1.5 1.5 0 1 0 .55 2.05 1.5 1.5 0 0 0-.55-2.05z" />
<path d="M6.16 11.26a1.5 1.5 0 1 0 2.08.4 1.49 1.49 0 0 0-2.08-.4z" />
</svg>
</button>
<button id="toggle_color_channels_section_button" aria-pressed="true" class="ui_icon_button trn" title="Toggle Color Channels">
<span class="sr_only">Toggle Color Channels</span>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-list" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
<path fill-rule="evenodd" d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/>
<circle cx="3.5" cy="5.5" r=".5"/>
<circle cx="3.5" cy="8" r=".5"/>
<circle cx="3.5" cy="10.5" r=".5"/>
</svg>
</button>
<button id="toggle_color_swatches_section_button" aria-pressed="true" class="ui_icon_button trn" title="Toggle Swatches">
<span class="sr_only">Toggle Swatches</span>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-grid-3x2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M0 3.5A1.5 1.5 0 0 1 1.5 2h13A1.5 1.5 0 0 1 16 3.5v8a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 11.5v-8zM1.5 3a.5.5 0 0 0-.5.5V7h4V3H1.5zM5 8H1v3.5a.5.5 0 0 0 .5.5H5V8zm1 0h4v4H6V8zm4-1H6V3h4v4zm1 1v4h3.5a.5.5 0 0 0 .5-.5V8h-4zm0-1V3h3.5a.5.5 0 0 1 .5.5V7h-4z"/>
</svg>
</button>
</div>
</div>
<div id="color_section_swatches" class="block_section">
<div id="color_swatches"></div>
</div>
<div id="color_section_picker" class="block_section">
<input id="color_picker_gradient" type="color" aria-label="Color Selection">
<div class="ui_input_group stacked">
<label id="color_hex_label" title="Hex" class="label_width_small trn">Hex</label>
<input id="color_hex" aria-labelledby="color_hex_label" value="#000000" maxlength="7" type="text" />
</div>
</div>
<div id="color_section_channels" class="block_section color_section_channels">
<div class="ui_input_grid stacked">
<div class="ui_input_group">
<label id="rgb_r_label" title="Red" class="label_width_character text_red"><strong>R<span class="sr_only">ed</span></strong></label>
<input id="rgb_r_range" aria-labelledby="rgb_r_label" type="range" min="0" max="255" class="color_picker" />
<input id="rgb_r" min="0" aria-labelledby="rgb_r_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="rgb_g_label" title="Green" class="label_width_character text_green"><strong>G<span class="sr_only">reen</span></strong></label>
<input id="rgb_g_range" aria-labelledby="rgb_g_label" type="range" min="0" max="255" class="color_picker" />
<input id="rgb_g" min="0" aria-labelledby="rgb_g_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="rgb_b_label" title="Blue" class="label_width_character text_blue"><strong>B<span class="sr_only">lue</span></strong></label>
<input id="rgb_b_range" aria-labelledby="rgb_b_label" type="range" min="0" max="255" class="color_picker" />
<input id="rgb_b" min="0" aria-labelledby="rgb_b_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="rgb_a_label" title="Alpha" class="label_width_character text_muted"><strong>A<span class="sr_only">lpha</span></strong></label>
<input id="rgb_a_range" aria-labelledby="rgb_a_label" type="range" min="0" max="255" class="color_picker" />
<input id="rgb_a" min="0" aria-labelledby="rgb_a_label" max="255" type="number" class="input_cw_3" />
</div>
</div>
<div class="ui_input_grid stacked">
<div class="ui_input_group">
<label id="hsl_h_label" title="Hue" class="label_width_character"><strong>H<span class="sr_only">ue</span></strong></label>
<input id="hsl_h_range" aria-labelledby="hsl_h_label" type="range" min="0" max="360" class="color_picker" />
<input id="hsl_h" min="0" aria-labelledby="hsl_h_label" max="360" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="hsl_s_label" title="Saturation" class="label_width_character"><strong>S<span class="sr_only">aturation</span></strong></label>
<input id="hsl_s_range" aria-labelledby="hsl_s_label" type="range" min="0" max="100" class="color_picker" />
<input id="hsl_s" min="0" aria-labelledby="hsl_s_label"max="100" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="hsl_l_label" title="Luminosity" class="label_width_character"><strong>L<span class="sr_only">uminosity</span></strong></label>
<input id="hsl_l_range" aria-labelledby="hsl_l_label" type="range" min="0" max="100" class="color_picker" />
<input id="hsl_l" min="0" aria-labelledby="hsl_l_label"max="100" type="number" class="input_cw_3" />
</div>
</div>
</div>
`;
const dialogTemplate = `
<div class="ui_flex_group">
<div id="dialog_color_picker_group" class="ui_flex_group column">
<input id="dialog_color_picker_gradient" type="color" aria-label="Color Selection">
<div class="block_section">
<div class="ui_input_grid stacked">
<div class="ui_input_group">
<label class="label_width_medium trn">Current</label>
<div id="dialog_selected_color_sample" class="ui_color_sample"></div>
</div>
<div class="ui_input_group">
<label class="label_width_medium trn">Previous</label>
<div id="dialog_previous_color_sample" class="ui_color_sample"></div>
</div>
</div>
</div>
</div>
<div id="dialog_color_channel_group">
<div class="ui_input_group stacked">
<label id="dialog_color_hex_label" title="Hex" class="label_width_small trn">Hex</label>
<input id="dialog_color_hex" aria-labelledby="dialog_color_hex_label" value="#000000" maxlength="7" type="text" />
</div>
<div class="ui_input_grid stacked">
<div class="ui_input_group">
<label id="dialog_rgb_r_label" title="Red" class="label_width_character text_red"><strong>R<span class="sr_only">ed</span></strong></label>
<input id="dialog_rgb_r_range" aria-labelledby="dialog_rgb_r_label" type="range" min="0" max="255" class="color_picker" />
<input id="dialog_rgb_r" min="0" aria-labelledby="dialog_rgb_r_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="dialog_rgb_g_label" title="Green" class="label_width_character text_green"><strong>G<span class="sr_only">reen</span></strong></label>
<input id="dialog_rgb_g_range" aria-labelledby="dialog_rgb_g_label" type="range" min="0" max="255" class="color_picker" />
<input id="dialog_rgb_g" min="0" aria-labelledby="dialog_rgb_g_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="dialog_rgb_b_label" title="Blue" class="label_width_character text_blue"><strong>B<span class="sr_only">lue</span></strong></label>
<input id="dialog_rgb_b_range" aria-labelledby="dialog_rgb_b_label" type="range" min="0" max="255" class="color_picker" />
<input id="dialog_rgb_b" min="0" aria-labelledby="dialog_rgb_b_label" max="255" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="dialog_rgb_a_label" title="Alpha" class="label_width_character text_muted"><strong>A<span class="sr_only">lpha</span></strong></label>
<input id="dialog_rgb_a_range" aria-labelledby="dialog_rgb_a_label" type="range" min="0" max="255" class="color_picker" />
<input id="dialog_rgb_a" min="0" aria-labelledby="dialog_rgb_a_label" max="255" type="number" class="input_cw_3" />
</div>
</div>
<div class="ui_input_grid stacked">
<div class="ui_input_group">
<label id="dialog_hsl_h_label" title="Hue" class="label_width_character"><strong>H<span class="sr_only">ue</span></strong></label>
<input id="dialog_hsl_h_range" aria-labelledby="dialog_hsl_h_label" type="range" min="0" max="360" class="color_picker" />
<input id="dialog_hsl_h" min="0" aria-labelledby="dialog_hsl_h_label" max="360" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="dialog_hsl_s_label" title="Saturation" class="label_width_character"><strong>S<span class="sr_only">aturation</span></strong></label>
<input id="dialog_hsl_s_range" aria-labelledby="dialog_hsl_s_label" type="range" min="0" max="100" class="color_picker" />
<input id="dialog_hsl_s" min="0" aria-labelledby="dialog_hsl_s_label"max="100" type="number" class="input_cw_3" />
</div>
<div class="ui_input_group">
<label id="dialog_hsl_l_label" title="Luminosity" class="label_width_character"><strong>L<span class="sr_only">uminosity</span></strong></label>
<input id="dialog_hsl_l_range" aria-labelledby="dialog_hsl_l_label" type="range" min="0" max="100" class="color_picker" />
<input id="dialog_hsl_l" min="0" aria-labelledby="dialog_hsl_l_label"max="100" type="number" class="input_cw_3" />
</div>
</div>
<div class="block_section">
<div id="dialog_color_swatches"></div>
</div>
</div>
</div>
`;
/**
* GUI class responsible for rendering colors block on right sidebar
*/
class GUI_colors_class {
constructor() {
this.el = null;
this.COLOR = '#000000';
this.ALPHA = 255;
this.colorNotSet = true;
this.uiType = null;
this.butons = null;
this.sections = null;
this.inputs = null;
this.Helper = new Helper_class();
this.Tools_translate = new Tools_translate_class();
}
render_main_colors(uiType) {
this.uiType = uiType || 'sidebar';
if (this.uiType === 'dialog') {
this.el = document.getElementById('dialog_color_picker');
this.el.innerHTML = dialogTemplate;
} else {
var saved_color = this.Helper.getCookie('color');
if (saved_color != null) config.COLOR = saved_color;
this.el = document.getElementById('toggle_colors');
this.el.innerHTML = sidebarTemplate;
}
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, this.el);
}
this.init_components();
this.render_ui_deferred = Helper.throttle(this.render_ui_deferred, 50);
}
init_components() {
// Store button references
this.buttons = {
toggleColorSwatches: $('#toggle_color_swatches_section_button', this.el),
toggleColorPicker: $('#toggle_color_picker_section_button', this.el),
toggleColorChannels: $('#toggle_color_channels_section_button', this.el)
};
// Store UI section references
this.sections = {
swatches: $('#color_section_swatches', this.el),
swatchesPlaceholder: document.createComment('Placeholder comment for color swatches'),
picker: $('#color_section_picker', this.el),
pickerPlaceholder: document.createComment('Placeholder comment for color picker'),
channels: $('#color_section_channels', this.el),
channelsPlaceholder: document.createComment('Placeholder comment for color channels')
};
// Store references to all inputs in DOM
const idPrefix = this.uiType === 'dialog' ? 'dialog_' : '';
this.inputs = {
sample: $(`#${idPrefix}selected_color_sample`, this.el),
swatches: $(`#${idPrefix}color_swatches`, this.el),
pickerGradient: $(`#${idPrefix}color_picker_gradient`, this.el),
hex: $(`#${idPrefix}color_hex`, this.el),
rgb: {
r: {
range: $(`#${idPrefix}rgb_r_range`, this.el),
number: $(`#${idPrefix}rgb_r`, this.el)
},
g: {
range: $(`#${idPrefix}rgb_g_range`, this.el),
number: $(`#${idPrefix}rgb_g`, this.el)
},
b: {
range: $(`#${idPrefix}rgb_b_range`, this.el),
number: $(`#${idPrefix}rgb_b`, this.el)
},
a: {
range: $(`#${idPrefix}rgb_a_range`, this.el),
number: $(`#${idPrefix}rgb_a`, this.el)
}
},
hsl: {
h: {
range: $(`#${idPrefix}hsl_h_range`, this.el),
number: $(`#${idPrefix}hsl_h`, this.el)
},
s: {
range: $(`#${idPrefix}hsl_s_range`, this.el),
number: $(`#${idPrefix}hsl_s`, this.el)
},
l: {
range: $(`#${idPrefix}hsl_l_range`, this.el),
number: $(`#${idPrefix}hsl_l`, this.el)
}
}
};
// Handle toggle for color swatches section
this.buttons.toggleColorSwatches
.on('click', () => {
this.buttons.toggleColorSwatches.attr('aria-pressed', 'true' === this.buttons.toggleColorSwatches.attr('aria-pressed') ? 'false' : 'true');
const isPressed = this.buttons.toggleColorSwatches.attr('aria-pressed') === 'true';
if (isPressed) {
this.sections.swatchesPlaceholder.parentNode.insertBefore(this.sections.swatches[0], this.sections.swatchesPlaceholder.nextSibling);
this.sections.swatchesPlaceholder.parentNode.removeChild(this.sections.swatchesPlaceholder);
} else {
this.sections.swatches[0].parentNode.insertBefore(this.sections.swatchesPlaceholder, this.sections.swatches[0].nextSibling);
this.sections.swatches[0].parentNode.removeChild(this.sections.swatches[0]);
}
Helper.setCookie('toggle_color_swatches', isPressed ? 1 : 0);
});
// Restore toggle preference, default to hidden for swatches
const saved_toggle_color_swatches = Helper.getCookie('toggle_color_swatches');
if (saved_toggle_color_swatches === 0 || saved_toggle_color_swatches == null) {
this.buttons.toggleColorSwatches.trigger('click');
}
// Handle toggle for color picker section
this.buttons.toggleColorPicker
.on('click', () => {
this.buttons.toggleColorPicker.attr('aria-pressed', 'true' === this.buttons.toggleColorPicker.attr('aria-pressed') ? 'false' : 'true');
const isPressed = this.buttons.toggleColorPicker.attr('aria-pressed') === 'true';
if (isPressed) {
this.sections.pickerPlaceholder.parentNode.insertBefore(this.sections.picker[0], this.sections.pickerPlaceholder.nextSibling);
this.sections.pickerPlaceholder.parentNode.removeChild(this.sections.pickerPlaceholder);
} else {
this.sections.picker[0].parentNode.insertBefore(this.sections.pickerPlaceholder, this.sections.picker[0].nextSibling);
this.sections.picker[0].parentNode.removeChild(this.sections.picker[0]);
}
Helper.setCookie('toggle_color_picker', isPressed ? 1 : 0);
});
this.inputs.sample.on('click', (event) => {
this.buttons.toggleColorPicker.click();
});
// Restore toggle preference, default to visible for picker
const saved_toggle_color_picker = Helper.getCookie('toggle_color_picker');
if (saved_toggle_color_picker === 0) {
this.buttons.toggleColorPicker.trigger('click');
}
// Handle toggle for color channels section
this.buttons.toggleColorChannels
.on('click', () => {
this.buttons.toggleColorChannels.attr('aria-pressed', 'true' === this.buttons.toggleColorChannels.attr('aria-pressed') ? 'false' : 'true');
const isPressed = this.buttons.toggleColorChannels.attr('aria-pressed') === 'true';
if (isPressed) {
this.sections.channelsPlaceholder.parentNode.insertBefore(this.sections.channels[0], this.sections.channelsPlaceholder.nextSibling);
this.sections.channelsPlaceholder.parentNode.removeChild(this.sections.channelsPlaceholder);
} else {
this.sections.channels[0].parentNode.insertBefore(this.sections.channelsPlaceholder, this.sections.channels[0].nextSibling);
this.sections.channels[0].parentNode.removeChild(this.sections.channels[0]);
}
Helper.setCookie('toggle_color_channels', isPressed ? 1 : 0);
});
// Restore toggle preference, default to hidden for swatches
const saved_toggle_color_channels = Helper.getCookie('toggle_color_channels');
if (saved_toggle_color_channels === 0 || saved_toggle_color_channels == null) {
this.buttons.toggleColorChannels.trigger('click');
}
// Initialize color swatches
this.inputs.swatches
.uiSwatches({ rows: 3, cols: 7, count: 21, readonly: this.uiType === 'dialog' })
.on('input', () => {
this.set_color({
hex: this.inputs.swatches.uiSwatches('get_selected_hex')
});
});
if (this.uiType === 'dialog') {
this.inputs.swatches.uiSwatches('set_all_hex', config.swatches.default);
}
// Initialize color picker gradient
this.inputs.pickerGradient
.uiColorPickerGradient()
.on('input', () => {
const hsv = this.inputs.pickerGradient.uiColorPickerGradient('get_hsv');
this.set_color({
h: hsv.h * 360,
s: hsv.s * 100,
v: hsv.v * 100
});
});
// Initialize hex entry
this.inputs.hex
.on('input', (event) => {
const value = this.inputs.hex.val();
const trimmedValue = value.trim();
if (value !== trimmedValue) {
this.inputs.hex.val(trimmedValue);
}
this.inputs.hex[0].setCustomValidity(/^\#[0-9A-F]{6}$/gi.test(trimmedValue) ? '' : 'Invalid Hex Code');
this.set_color({ hex: this.inputs.hex.val() });
})
.on('blur', () => {
const value = this.inputs.hex.val();
if (!/^\#[0-9A-F]{6}$/gi.test(value)) {
this.inputs.hex.val(this.uiType === 'dialog' ? this.COLOR : config.COLOR);
this.inputs.hex[0].setCustomValidity('');
}
});
// Initialize the color sliders
const sliderInputs = [
...Object.entries(this.inputs.rgb),
...Object.entries(this.inputs.hsl)
];
for (const [key, input] of sliderInputs) {
input.range && input.range
.uiRange()
.on('input', () => {
this.set_color({ [key]: input.range.uiRange('get_value') });
});
input.number && input.number
.uiNumberInput()
.on('input', () => {
this.set_color({ [key]: input.number.uiNumberInput('get_value') });
})
}
// Update all inputs from config.COLOR
this.render_selected_color();
}
/**
* Changes the config.COLOR variable based on the given input.
* @param {*} definition object contains the value of the color to change:
* hex - set the color as a hex code
* r,g,b - set the color as red, green, blue values [0-255]
* a - set the color alpha [0-255]
* h,s,l - set the color as hue [0-360], saturation [0-100], luminosity [0-100]
* h,s,v - set the color as hue [0-360], saturation [0-100], value [0-100]
*/
set_color(definition) {
let newColor = null;
let newAlpha = null;
let hsl = null;
let hsv = null;
// Set new color by hex code
if ('hex' in definition) {
const hex = '#' + definition.hex.replace(/[^0-9A-F]*/gi, '');
if (/^\#[0-9A-F]{6}$/gi.test(hex)) {
newColor = '#' + definition.hex.trim().replace(/^\#/, '');
}
}
// Set new color by rgb
else if ('r' in definition || 'b' in definition || 'g' in definition) {
const previousRgb = Helper.hexToRgb(this.uiType === 'dialog' ? this.COLOR : config.COLOR);
newColor = Helper.rgbToHex(
'r' in definition ? Math.min(255, Math.max(0, parseInt(definition.r, 10) || 0)) : previousRgb.r,
'g' in definition ? Math.min(255, Math.max(0, parseInt(definition.g, 10) || 0)) : previousRgb.g,
'b' in definition ? Math.min(255, Math.max(0, parseInt(definition.b, 10) || 0)) : previousRgb.b
);
}
// Set new color by hsv
else if ('v' in definition) {
const previousRgb = Helper.hexToRgb(this.uiType === 'dialog' ? this.COLOR : config.COLOR);
const previousHsv = Helper.rgbToHsv(previousRgb.r, previousRgb.g, previousRgb.b);
hsv = {
h: 'h' in definition ? Math.min(360, Math.max(0, parseInt(definition.h, 10) || 0)) / 360 : previousHsv.h,
s: 's' in definition ? Math.min(100, Math.max(0, parseInt(definition.s, 10) || 0)) / 100 : previousHsv.s,
v: 'v' in definition ? Math.min(100, Math.max(0, parseInt(definition.v, 10) || 0)) / 100 : previousHsv.v
};
newColor = Helper.hsvToHex(hsv.h, hsv.s, hsv.v);
}
// Set new color by hsl
else if ('h' in definition || 's' in definition || 'l' in definition) {
hsl = {
h: ('h' in definition ? Math.min(360, Math.max(0, parseInt(definition.h, 10) || 0)) : parseInt(this.inputs.hsl.h.number.uiNumberInput('get_value'), 10)) / 360,
s: ('s' in definition ? Math.min(100, Math.max(0, parseInt(definition.s, 10) || 0)) : parseInt(this.inputs.hsl.s.number.uiNumberInput('get_value'), 10)) / 100,
l: ('l' in definition ? Math.min(100, Math.max(0, parseInt(definition.l, 10) || 0)) : parseInt(this.inputs.hsl.l.number.uiNumberInput('get_value'), 10)) / 100
};
newColor = Helper.hslToHex(hsl.h, hsl.s, hsl.l);
}
// Set new alpha
if ('a' in definition) {
newAlpha = Math.min(255, Math.max(0, parseInt(Math.ceil(definition.a), 10)));
}
// Re-render UI if changes made
if (newColor != null || newAlpha != null) {
if (this.uiType === 'dialog') {
this.COLOR = newColor != null ? newColor : this.COLOR;
this.ALPHA = newAlpha != null ? newAlpha : this.ALPHA;
if (this.colorNotSet) {
this.colorNotSet = false;
$('#dialog_previous_color_sample', this.el)[0].style.background = this.COLOR;
}
} else {
config.COLOR = newColor != null ? newColor : config.COLOR;
config.ALPHA = newAlpha != null ? newAlpha : config.ALPHA;
}
if (hsl && !hsv) {
hsv = Helper.hslToHsv(hsl.h, hsl.s, hsl.l);
}
if (hsv && !hsl) {
hsl = Helper.hsvToHsl(hsv.h, hsv.s, hsv.v);
}
this.render_selected_color({ hsl, hsv });
}
if (this.uiType === 'sidebar') {
this.Helper.setCookie('color', config.COLOR);
}
}
/**
* Renders current color defined in the config to all color fields
* @param {*} options additional options:
* hsl - override for hsl values so it isn't calculated based on rgb (can lose selected hue/saturation otherwise)
* hsv - override for hsv values so it isn't calculated based on rgb (can lose selected hue/saturation otherwise)
*/
render_selected_color(options) {
options = options || {};
const COLOR = this.uiType === 'dialog' ? this.COLOR : config.COLOR;
const ALPHA = this.uiType === 'dialog' ? this.ALPHA : config.ALPHA;
this.inputs.sample.css('background', COLOR);
if (this.uiType !== 'dialog') {
this.inputs.swatches.uiSwatches('set_selected_hex', COLOR);
}
const hexInput = this.inputs.hex[0];
hexInput.value = COLOR;
hexInput.setCustomValidity('');
const rgb = Helper.hexToRgb(COLOR);
delete rgb.a;
for (let rgbKey in rgb) {
this.inputs.rgb[rgbKey].range.uiRange('set_value', rgb[rgbKey]);
this.inputs.rgb[rgbKey].number.uiNumberInput('set_value', rgb[rgbKey]);
}
this.inputs.rgb.a.range.uiRange('set_value', ALPHA);
this.inputs.rgb.a.number.uiNumberInput('set_value', ALPHA);
const hsv = options.hsv || Helper.rgbToHsv(rgb.r, rgb.g, rgb.b);
const hsl = options.hsl || Helper.rgbToHsl(rgb.r, rgb.g, rgb.b);
for (let hslKey in hsl) {
const hslValue = Math.round(hsl[hslKey] * (hslKey === 'h' ? 360 : 100));
this.inputs.hsl[hslKey].range.uiRange('set_value', hslValue);
this.inputs.hsl[hslKey].number.uiNumberInput('set_value', hslValue);
}
this.render_ui_deferred({ hsl, hsv });
}
/**
* Renders the color gradients in each channel's color range selection.
* This function is throttled due to expensive operations on low-end systems.
* @param {*} options additional options:
* hsl - override for hsl values so it isn't calculated based on rgb (can lose selected hue/saturation otherwise)
* hsv - override for hsv values so it isn't calculated based on rgb (can lose selected hue/saturation otherwise)
*/
render_ui_deferred(options) {
options = options || {};
const COLOR = this.uiType === 'dialog' ? this.COLOR : config.COLOR;
// RGB
const rgb = Helper.hexToRgb(COLOR);
delete rgb.a;
for (let rgbKey in rgb) {
const rangeMin = JSON.parse(JSON.stringify(rgb));
const rangeMax = JSON.parse(JSON.stringify(rgb));
rangeMin[rgbKey] = 0;
rangeMax[rgbKey] = 255;
this.inputs.rgb[rgbKey].range.uiRange('set_background',
`linear-gradient(to right, ${ Helper.rgbToHex(rangeMin.r, rangeMin.g, rangeMin.b) }, ${ Helper.rgbToHex(rangeMax.r, rangeMax.g, rangeMax.b) })`
);
}
// A
this.inputs.rgb.a.range.uiRange('set_background',
`linear-gradient(to right, transparent, ${ COLOR })`
);
// HSV
const hsv = options.hsv || Helper.rgbToHsv(rgb.r, rgb.g, rgb.b);
this.inputs.pickerGradient.uiColorPickerGradient('set_hsv', hsv);
// HSL
const hsl = options.hsl || Helper.rgbToHsl(rgb.r, rgb.g, rgb.b);
// HSL - H
this.inputs.hsl.h.range.uiRange('set_background',
`linear-gradient(to right, ${
Helper.hex_set_hsl('#ff0000', { s: hsl.s, l: hsl.l })
} 0%, ${
Helper.hex_set_hsl('#ffff00', { s: hsl.s, l: hsl.l })
} 17%, ${
Helper.hex_set_hsl('#00ff00', { s: hsl.s, l: hsl.l })
} 33%, ${
Helper.hex_set_hsl('#00ffff', { s: hsl.s, l: hsl.l })
} 50%, ${
Helper.hex_set_hsl('#0000ff', { s: hsl.s, l: hsl.l })
} 67%, ${
Helper.hex_set_hsl('#ff00ff', { s: hsl.s, l: hsl.l })
} 83%, ${
Helper.hex_set_hsl('#ff0000', { s: hsl.s, l: hsl.l })
} 100%)`
);
// HSL - S
let rangeMin = JSON.parse(JSON.stringify(hsl));
let rangeMax = JSON.parse(JSON.stringify(hsl));
rangeMin.s = 0;
rangeMax.s = 1;
this.inputs.hsl.s.range.uiRange('set_background',
`linear-gradient(to right, ${ Helper.hslToHex(rangeMin.h, rangeMin.s, rangeMin.l) }, ${ Helper.hslToHex(rangeMax.h, rangeMax.s, rangeMax.l) })`
);
// HSL - L
let rangeMid = JSON.parse(JSON.stringify(hsl));
rangeMid.l = 0.5;
this.inputs.hsl.l.range.uiRange('set_background',
`linear-gradient(to right, #000000 0%, ${ Helper.hslToHex(rangeMid.h, rangeMid.s, rangeMid.l) } 50%, #ffffff 100%)`
);
// Store swatch values
if (this.uiType === 'sidebar') {
config.swatches.default = this.inputs.swatches.uiSwatches('get_all_hex');
}
}
}
export default GUI_colors_class;
+719
View File
@@ -0,0 +1,719 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import app from './../../app.js';
import config from './../../config.js';
import Dialog_class from './../../libs/popup.js';
import Text_class from './../../tools/text.js';
import Base_layers_class from "../base-layers";
import Tools_settings_class from './../../modules/tools/settings.js';
import Helper_class from './../../libs/helpers.js';
import Tools_translate_class from './../../modules/tools/translate.js';
var template = `
<div class="row">
<span class="trn label">X</span>
<input type="number" id="detail_x" step="any" />
<button class="extra reset trn" type="button" id="reset_x" title="Reset">Reset</button>
</div>
<div class="row">
<span class="trn label">Y:</span>
<input type="number" id="detail_y" step="any" />
<button class="extra reset trn" type="button" id="reset_y" title="Reset">Reset</button>
</div>
<div class="row">
<span class="trn label">Width:</span>
<input type="number" id="detail_width" step="any" />
<button class="extra reset trn" type="button" id="reset_size" title="Reset">Reset</button>
</div>
<div class="row">
<span class="trn label">Height:</span>
<input type="number" id="detail_height" step="any" />
</div>
<hr />
<div class="row">
<span class="trn label">Rotate:</span>
<input type="number" min="-360" max="360" id="detail_rotate" />
<button class="extra reset trn" type="button" id="reset_rotate" title="Reset">Reset</button>
</div>
<div class="row">
<span class="trn label">Opacity:</span>
<input type="number" min="0" max="100" id="detail_opacity" />
<button class="extra reset trn" type="button" id="reset_opacity" title="Reset">Reset</button>
</div>
<div class="row">
<span class="trn label">Color:</span>
<input style="padding: 0px;" type="color" id="detail_color" />
</div>
<div id="parameters_container"></div>
<div id="text_detail_params">
<div class="row center">
<span class="trn label">&nbsp;</span>
<button type="button" class="trn dots" id="detail_param_text">Edit text...</button>
</div>
<div class="row">
<span class="trn label" title="Resize Boundary">Bounds:</span>
<select id="detail_param_boundary">
<option value="box">Box</option>
<option value="dynamic">Dynamic</option>
</select>
</div>
<div class="row">
<span class="trn label" title="Auto Kerning">Kerning:</span>
<select id="detail_param_kerning">
<option value="none">None</option>
<option value="metrics">Metrics</option>
</select>
</div>
<div class="row" hidden> <!-- Future implementation -->
<span class="trn label">Direction:</span>
<select id="detail_param_text_direction">
<option value="ltr">Left to Right</option>
<option value="rtl">Right to Left</option>
<option value="ttb">Top to Bottom</option>
<option value="btt">Bottom to Top</option>
</select>
</div>
<div class="row" hidden> <!-- Future implementation -->
<span class="trn label">Wrap:</span>
<select id="detail_param_wrap_direction">
<option value="ltr">Left to Right</option>
<option value="rtl">Right to Left</option>
<option value="ttb">Top to Bottom</option>
<option value="btt">Bottom to Top</option>
</select>
</div>
<div class="row">
<span class="trn label">Wrap At:</span>
<select id="detail_param_wrap">
<option value="letter">Word + Letter</option>
<option value="word">Word</option>
</select>
</div>
<div class="row">
<span class="trn label" title="Horizontal Alignment">H. Align:</span>
<select id="detail_param_halign">
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
</select>
</div>
<div class="row" hidden> <!-- Future implementation -->
<span class="trn label" title="Vertical Alignment">V. Align:</span>
<select id="detail_param_valign">
<option value="top">Top</option>
<option value="middle">Middle</option>
<option value="bottom">Bottom</option>
</select>
</div>
<div>
`;
/**
* GUI class responsible for rendering selected layer details block on right sidebar
*/
class GUI_details_class {
constructor() {
this.POP = new Dialog_class();
this.Text = new Text_class();
this.Base_layers = new Base_layers_class();
this.Tools_settings = new Tools_settings_class();
this.Helper = new Helper_class();
this.layer_details_active = false;
this.Tools_translate = new Tools_translate_class();
}
render_main_details() {
document.getElementById('toggle_details').innerHTML = template;
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, document.getElementById('toggle_details'));
}
this.render_details(true);
}
render_details(events = false) {
this.render_general('x', events);
this.render_general('y', events);
this.render_general('width', events);
this.render_general('height', events);
this.render_general('rotate', events);
this.render_general('opacity', events);
this.render_color(events);
this.render_reset(events);
//text - special case
if (config.layer != undefined && config.layer.type == 'text') {
document.getElementById('text_detail_params').style.display = 'block';
document.getElementById('detail_color').closest('.row').style.display = 'none';
}
else{
document.getElementById('text_detail_params').style.display = 'none';
if (config.layer != undefined && (config.layer.color === null || config.layer.type == 'image')) {
//hide color
document.getElementById('detail_color').closest('.row').style.display = 'none';
}
else {
//show color
document.getElementById('detail_color').closest('.row').style.display = 'block';
}
}
//add params
this.render_more_parameters();
this.render_text(events);
this.render_general_select_param('boundary', events);
this.render_general_select_param('kerning', events);
this.render_general_select_param('text_direction', events);
this.render_general_select_param('wrap', events);
this.render_general_select_param('wrap_direction', events);
this.render_general_select_param('halign', events);
this.render_general_select_param('valign', events);
}
render_general(key, events) {
var layer = config.layer;
var _this = this;
var units = this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution');
if (layer != undefined) {
var target = document.getElementById('detail_' + key);
target.dataset.layer = layer.id;
if (layer[key] == null) {
target.value = '';
target.disabled = true;
}
else {
var value = layer[key];
if(key == 'x' || key == 'y' || key == 'width' || key == 'height'){
//convert units
value = this.Helper.get_user_unit(value, units, resolution);
}
else {
value = Math.round(value);
}
//set
target.value = value;
target.disabled = false;
}
}
if (events) {
//events
var target = document.getElementById('detail_' + key);
if(target == undefined){
console.log('Error: missing details event target ' + 'detail_' + key);
return;
}
let focus_value = null;
target.addEventListener('focus', function (e) {
focus_value = parseFloat(this.value);
});
target.addEventListener('blur', function (e) {
if(key == 'x' || key == 'y' || key == 'width' || key == 'height'){
//convert units
var value = _this.Helper.get_internal_unit(this.value, units, resolution);
}
else {
var value = parseInt(this.value);
}
var layer = _this.Base_layers.get_layer(e.target.dataset.layer);
layer[key] = focus_value;
if (focus_value !== value) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(layer.id, {
[key]: value
})
])
);
}
});
target.addEventListener('change', function (e) {
if(key == 'x' || key == 'y' || key == 'width' || key == 'height'){
//convert units
var value = _this.Helper.get_internal_unit(this.value, units, resolution);
}
else {
var value = parseInt(this.value);
}
if(this.min != undefined && this.min != '' && value < this.min){
document.getElementById('detail_opacity').value = value;
value = this.min;
}
if(this.max != undefined && this.min != '' && value > this.max){
document.getElementById('detail_opacity').value = value;
value = this.max;
}
config.layer[key] = value;
config.need_render = true;
});
target.addEventListener('keyup', function (e) {
//for edge....
if (e.keyCode != 13) {
return;
}
if(key == 'x' || key == 'y' || key == 'width' || key == 'height'){
//convert units
var value = _this.Helper.get_internal_unit(this.value, units, resolution);
}
else {
var value = parseInt(this.value);
}
if(this.min != undefined && this.min != '' && value < this.min){
document.getElementById('detail_opacity').value = value;
value = this.min;
}
if(this.max != undefined && this.min != '' && value > this.max){
document.getElementById('detail_opacity').value = value;
value = this.max;
}
config.layer[key] = value;
config.need_render = true;
});
}
}
render_general_param(key, events) {
var layer = config.layer;
if (layer != undefined) {
var target = document.getElementById('detail_param_' + key);
if (layer.params[key] == null) {
target.value = '';
target.disabled = true;
}
else {
if (typeof layer.params[key] == 'boolean') {
//boolean
if(target.tagName == 'BUTTON'){
if(layer.params[key]){
target.classList.add('active');
}
else{
target.classList.remove('active');
}
}
}
else {
//common
target.value = layer.params[key];
}
target.disabled = false;
}
}
if (events) {
//events
var target = document.getElementById('detail_param_' + key);
let focus_value = null;
target.addEventListener('focus', function (e) {
focus_value = parseInt(this.value);
});
target.addEventListener('blur', function (e) {
var value = parseInt(this.value);
config.layer.params[key] = focus_value;
let params_copy = JSON.parse(JSON.stringify(config.layer.params));
params_copy[key] = value;
if (focus_value !== value) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
params: params_copy
})
])
);
}
});
target.addEventListener('change', function (e) {
var value = parseInt(this.value);
config.layer.params[key] = value;
config.need_render = true;
config.need_render_changed_params = true;
});
target.addEventListener('click', function (e) {
if (typeof config.layer.params[key] != 'boolean')
return;
this.classList.toggle('active');
config.layer.params[key] = !config.layer.params[key];
config.need_render = true;
config.need_render_changed_params = true;
});
}
}
render_general_select_param(key, events){
var layer = config.layer;
if (layer != undefined) {
var target = document.getElementById('detail_param_' + key);
if (layer.params[key] == null) {
target.value = '';
target.disabled = true;
}
else {
if(typeof layer.params[key] == 'object')
target.value = layer.params[key].value; //legacy
else
target.value = layer.params[key];
target.disabled = false;
}
}
if (events) {
//events
var target = document.getElementById('detail_param_' + key);
let focus_value = null;
target.addEventListener('focus', function (e) {
focus_value = this.value;
});
target.addEventListener('blur', function (e) {
var value = this.value;
config.layer.params[key] = focus_value;
let params_copy = JSON.parse(JSON.stringify(config.layer.params));
params_copy[key] = value;
if (focus_value !== value) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
params: params_copy
})
])
);
}
});
target.addEventListener('change', function (e) {
var value = this.value;
config.layer.params[key] = value;
config.need_render = true;
config.need_render_changed_params = true;
});
}
}
/**
* item: color
*/
render_color(events) {
var layer = config.layer;
let $colorInput;
if (events) {
$colorInput = $(document.getElementById('detail_color')).uiColorInput();
} else {
$colorInput = $(document.getElementById('detail_color'));
}
if (layer != undefined) {
$colorInput.uiColorInput('set_value', layer.color);
}
if (events) {
//events
let focus_value = null;
$colorInput.on('focus', function (e) {
focus_value = $colorInput.uiColorInput('get_value');
});
$colorInput.on('change', function (e) {
const value = $colorInput.uiColorInput('get_value');
config.layer.color = focus_value;
if (focus_value !== value) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
color: value
})
])
);
}
});
}
}
/**
* item: size reset button
*/
render_reset(events) {
var layer = config.layer;
if (layer != undefined) {
//size
if (layer.width_original != null) {
document.getElementById('reset_size').classList.remove('hidden');
}
else {
document.getElementById('reset_size').classList.add('hidden');
}
}
if (events) {
//events
document.getElementById('reset_x').addEventListener('click', function (e) {
if (config.layer.x) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
x: 0
})
])
);
}
});
document.getElementById('reset_y').addEventListener('click', function (e) {
if (config.layer.y) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
y: 0
})
])
);
}
});
document.getElementById('reset_size').addEventListener('click', function (e) {
if (config.layer.width !== config.layer.width_original
|| config.layer.height !== config.layer.height_original) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
width: config.layer.width_original,
height: config.layer.height_original
})
])
);
}
});
document.getElementById('reset_rotate').addEventListener('click', function (e) {
if (config.layer.rotate) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
rotate: 0
})
])
);
}
});
document.getElementById('reset_opacity').addEventListener('click', function (e) {
if (config.layer.opacity != 100) {
app.State.do_action(
new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [
new app.Actions.Update_layer_action(config.layer.id, {
opacity: 100
})
])
);
}
});
}
}
/**
* item: text
*/
render_text(events) {
if (events) {
//events
document.getElementById('detail_param_text').addEventListener('click', function (e) {
document.querySelector('#tools_container #text').click();
document.getElementById('text_tool_keyboard_input').focus();
config.need_render = true;
});
}
}
render_more_parameters() {
var _this = this;
var target_id = "parameters_container";
const itemContainer = document.getElementById(target_id);
if(this.layer_details_active == true){
return;
}
itemContainer.innerHTML = "";
if(!config.layer || typeof config.layer.params == 'undefined' || config.layer.type == 'text') {
return;
}
//find layer parameters settings
var params_config = null;
for (var i in config.TOOLS) {
if (config.TOOLS[i].name == config.layer.type) {
params_config = config.TOOLS[i];
}
}
if(params_config == null){
return;
}
for (var k in params_config.attributes) {
var item = params_config.attributes[k];
//hide some fields, in future name should start with underscore
if(params_config.name == 'rectangle' && k == 'square'
|| params_config.name == 'ellipse' && k == 'circle'
|| params_config.name == 'pencil' && k == 'pressure'
|| params_config.name == 'pencil' && k == 'size'){
continue;
}
//row
let item_row = document.createElement('div');
item_row.className = 'row';
itemContainer.appendChild(item_row);
//title
var title = k[0].toUpperCase() + k.slice(1);
title = title.replace("_", " ");
let item_title = document.createElement('span');
item_title.className = 'trn label';
item_title.innerHTML = title;
item_row.appendChild(item_title);
//value
if (typeof item == 'boolean' || (typeof item == 'object' && typeof item.value == 'boolean')) {
//boolean - true, false
const elementInput = document.createElement('button');
elementInput.type = 'button';
elementInput.className = 'trn ui_toggle_button';
elementInput.innerHTML = title;
elementInput.dataset.key = k;
item_row.appendChild(elementInput);
let value = config.layer.params[k];
elementInput.setAttribute('aria-pressed', value);
//events
elementInput.addEventListener('click', function (e) {
//on leave
let layer = config.layer;
let key = this.dataset.key;
let new_value = elementInput.getAttribute('aria-pressed') !== 'true';
let params = JSON.parse(JSON.stringify(config.layer.params));
params[key] = new_value;
app.State.do_action(
new app.Actions.Update_layer_action(layer.id, {
params: params
})
);
});
}
else if (typeof item == 'number' || (typeof item == 'object' && typeof item.value == 'number')) {
//numbers
const elementInput = document.createElement('input');
elementInput.type = 'number';
elementInput.dataset.key = k;
item_row.appendChild(elementInput);
let min = 1;
let max = k === 'power' ? 100 : 999;
let step = null;
let value = config.layer.params[k];
if (typeof item == 'object') {
value = item.value;
if (item.min != null) {
min = item.min;
}
if (item.max != null) {
max = item.max;
}
if (item.step != null) {
step = item.step;
}
}
elementInput.setAttribute('min', min);
elementInput.setAttribute('max', max);
if (item.step != null) {
elementInput.setAttribute('step', step);
}
elementInput.setAttribute('value', config.layer.params[k]);
//events
let focus_value = null;
elementInput.addEventListener('focus', function (e) {
focus_value = parseFloat(this.value);
_this.layer_details_active = true;
});
elementInput.addEventListener('blur', function (e) {
//on leave
_this.layer_details_active = false;
let layer = config.layer;
let key = this.dataset.key;
let new_value = parseInt(this.value);
let params = JSON.parse(JSON.stringify(config.layer.params));
params[key] = new_value;
if (focus_value !== new_value) {
app.State.do_action(
new app.Actions.Update_layer_action(layer.id, {
params: params
})
);
}
});
elementInput.addEventListener('change', function (e) {
//on change - lots of events here in short time
let key = this.dataset.key;
let new_value = parseInt(this.value);
config.layer.params[key] = new_value;
config.need_render = true;
});
}
else if (typeof item == 'string' && item[0] == '#') {
//color
var elementInput = document.createElement('input');
elementInput.type = 'color';
let focus_value = null;
const $colorInput = $(elementInput).uiColorInput({
id: k,
value: item
})
.on('change', () => {
let layer = config.layer;
let key = $colorInput.uiColorInput('get_id');
let new_value = $colorInput.uiColorInput('get_value');
let params = JSON.parse(JSON.stringify(config.layer.params));
params[key] = new_value;
app.State.do_action(
new app.Actions.Update_layer_action(layer.id, {
params: params
})
);
});
$colorInput.uiColorInput('set_value', config.layer.params[k]);
item_row.appendChild($colorInput[0]);
}
else {
alertify.error('Error: unsupported attribute type:' + typeof item + ', ' + k);
}
}
}
}
export default GUI_details_class;
+105
View File
@@ -0,0 +1,105 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../../config.js';
import Base_layers_class from './../base-layers.js';
import Tools_settings_class from './../../modules/tools/settings.js';
import Helper_class from './../../libs/helpers.js';
import Tools_translate_class from './../../modules/tools/translate.js';
var template = `
<span class="trn label">Size:</span>
<span id="mouse_info_size">-</span>
<span class="id-mouse_info_units"></span>
<br />
<span class="trn label">Mouse:</span>
<span id="mouse_info_mouse">-</span>
<span class="id-mouse_info_units"></span>
<br />
<span class="trn label">Resolution:</span>
<span id="mouse_info_resolution">-</span>
`;
/**
* GUI class responsible for rendering information block on right sidebar
*/
class GUI_information_class {
constructor(ctx) {
this.Base_layers = new Base_layers_class();
this.Tools_settings = new Tools_settings_class();
this.Helper = new Helper_class();
this.Tools_translate = new Tools_translate_class();
this.last_width = null;
this.last_height = null;
this.units = this.Tools_settings.get_setting('default_units');
this.resolution = this.Tools_settings.get_setting('resolution');
}
render_main_information() {
document.getElementById('toggle_info').innerHTML = template;
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, document.getElementById('toggle_info'));
}
this.set_events();
this.show_size();
}
set_events() {
var _this = this;
var target = document.getElementById('mouse_info_mouse');
//show width and height
//should use canvas resize API in future
document.addEventListener('mousemove', function (e) {
_this.show_size();
}, false);
//show current mouse position
document.getElementById('canvas_minipaint').addEventListener('mousemove', function (e) {
var global_pos = _this.Base_layers.get_world_coords(e.offsetX, e.offsetY);
var mouse_x = Math.ceil(global_pos.x);
var mouse_y = Math.ceil(global_pos.y);
mouse_x = _this.Helper.get_user_unit(mouse_x, _this.units, _this.resolution);
mouse_y = _this.Helper.get_user_unit(mouse_y, _this.units, _this.resolution);
target.innerHTML = mouse_x + ', ' + mouse_y;
}, false);
}
update_units(){
this.units = this.Tools_settings.get_setting('default_units');
this.resolution = this.Tools_settings.get_setting('resolution');
this.show_size(true);
}
show_size(force) {
if(force == undefined && this.last_width == config.WIDTH && this.last_height == config.HEIGHT) {
return;
}
var width = this.Helper.get_user_unit(config.WIDTH, this.units, this.resolution);
var height = this.Helper.get_user_unit(config.HEIGHT, this.units, this.resolution);
document.getElementById('mouse_info_size').innerHTML = width + ' x ' + height;
var resolution = this.Tools_settings.get_setting('resolution');
document.getElementById('mouse_info_resolution').innerHTML = resolution;
//show units
var default_units = this.Tools_settings.get_setting('default_units_short');
var targets = document.querySelectorAll('.id-mouse_info_units');
for (var i = 0; i < targets.length; i++) {
targets[i].innerHTML = default_units;
}
this.last_width = config.WIDTH;
this.last_height = config.HEIGHT;
}
}
export default GUI_information_class;
+200
View File
@@ -0,0 +1,200 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import app from './../../app.js';
import config from './../../config.js';
import Base_layers_class from './../base-layers.js';
import Helper_class from './../../libs/helpers.js';
import Layer_rename_class from './../../modules/layer/rename.js';
import Effects_browser_class from './../../modules/effects/browser.js';
import Layer_duplicate_class from './../../modules/layer/duplicate.js';
import Layer_raster_class from './../../modules/layer/raster.js';
import Tools_translate_class from './../../modules/tools/translate.js';
var template = `
<button type="button" class="layer_add trn" id="insert_layer" title="Insert new layer">+</button>
<button type="button" class="layer_duplicate trn" id="layer_duplicate" title="Duplicate layer">D</button>
<button type="button" class="layer_raster trn" id="layer_raster" title="Convert layer to raster">R</button>
<button type="button" class="layers_arrow trn" title="Move layer down" id="layer_down">&darr;</button>
<button type="button" class="layers_arrow trn" title="Move layer up" id="layer_up">&uarr;</button>
<div class="layers_list" id="layers"></div>
`;
/**
* GUI class responsible for rendering layers on right sidebar
*/
class GUI_layers_class {
constructor(ctx) {
this.Base_layers = new Base_layers_class();
this.Helper = new Helper_class();
this.Layer_rename = new Layer_rename_class();
this.Effects_browser = new Effects_browser_class();
this.Layer_duplicate = new Layer_duplicate_class();
this.Layer_raster = new Layer_raster_class();
this.Tools_translate = new Tools_translate_class();
}
render_main_layers() {
document.getElementById('layers_base').innerHTML = template;
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, document.getElementById('layers_base'));
}
this.render_layers();
this.set_events();
}
set_events() {
var _this = this;
document.getElementById('layers_base').addEventListener('click', function (event) {
var target = event.target;
if (target.id == 'insert_layer') {
//new layer
app.State.do_action(
new app.Actions.Insert_layer_action()
);
}
else if (target.id == 'layer_duplicate') {
//duplicate
_this.Layer_duplicate.duplicate();
}
else if (target.id == 'layer_raster') {
//raster
_this.Layer_raster.raster();
}
else if (target.id == 'layer_up') {
//move layer up
app.State.do_action(
new app.Actions.Reorder_layer_action(config.layer.id, 1)
);
}
else if (target.id == 'layer_down') {
//move layer down
app.State.do_action(
new app.Actions.Reorder_layer_action(config.layer.id, -1)
);
}
else if (target.id == 'visibility') {
//change visibility
return app.State.do_action(
new app.Actions.Toggle_layer_visibility_action(target.dataset.id)
);
}
else if (target.id == 'delete') {
//delete layer
app.State.do_action(
new app.Actions.Delete_layer_action(target.dataset.id)
);
}
else if (target.id == 'layer_name') {
//select layer
if (target.dataset.id == config.layer.id)
return;
app.State.do_action(
new app.Actions.Select_layer_action(target.dataset.id)
);
}
else if (target.id == 'delete_filter') {
//delete filter
app.State.do_action(
new app.Actions.Delete_layer_filter_action(target.dataset.pid, target.dataset.id)
);
}
else if (target.id == 'filter_name') {
//edit filter
var effects = _this.Effects_browser.get_effects_list();
var key = target.dataset.filter.toLowerCase();
for (var i in effects) {
if(effects[i].title.toLowerCase() == key){
_this.Base_layers.select(target.dataset.pid);
var function_name = _this.Effects_browser.get_function_from_path(key);
effects[i].object[function_name](target.dataset.id);
}
}
}
});
document.getElementById('layers_base').addEventListener('dblclick', function (event) {
var target = event.target;
if (target.id == 'layer_name') {
//rename layer
_this.Layer_rename.rename(target.dataset.id);
}
});
}
/**
* renders layers list
*/
render_layers() {
var target_id = 'layers';
var layers = config.layers.concat().sort(
//sort function
(a, b) => b.order - a.order
);
document.getElementById(target_id).innerHTML = '';
var html = '';
if (config.layer) {
for (var i in layers) {
var value = layers[i];
var class_extra = '';
if(value.composition === 'source-atop'){
class_extra += ' shorter';
}
if (value.id == config.layer.id){
class_extra += ' active';
}
html += '<div class="item ' + class_extra + '">';
if (value.visible == true)
html += ' <button class="visibility visible trn" id="visibility" data-id="' + value.id + '" title="Hide"></button>';
else
html += ' <button class="visibility trn" id="visibility" data-id="' + value.id + '" title="Show"></button>';
html += ' <button class="delete trn" id="delete" data-id="' + value.id + '" title="Delete"></button>';
if(value.composition === 'source-atop'){
html += ' <button class="arrow_down" data-id="' + value.id + '" ></button>';
}
var layer_title = this.Helper.escapeHtml(value.name);
html += ' <button class="layer_name" id="layer_name" data-id="' + value.id + '">' + layer_title + '</button>';
html += ' <div class="clear"></div>';
html += '</div>';
//show filters
if (layers[i].filters.length > 0) {
html += '<div class="filters">';
for (var j in layers[i].filters) {
var filter = layers[i].filters[j];
var title = this.Helper.ucfirst(filter.name);
title = title.replace(/-/g, ' ');
html += '<div class="filter">';
html += ' <span class="delete" id="delete_filter" data-pid="' + layers[i].id + '" data-id="' + filter.id + '" title="delete"></span>';
html += ' <span class="layer_name" id="filter_name" data-pid="' + layers[i].id + '" data-id="' + filter.id + '" data-filter="' + filter.name + '">' + title + '</span>';
html += ' <div class="clear"></div>';
html += '</div>';
}
html += '</div>';
}
}
}
//register
document.getElementById(target_id).innerHTML = html;
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, document.getElementById(target_id));
}
}
}
export default GUI_layers_class;
+419
View File
@@ -0,0 +1,419 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../../config.js';
import menuDefinition from './../../config-menu.js';
import Tools_translate_class from './../../modules/tools/translate.js';
/**
* class responsible for rendering main menu
*/
class GUI_menu_class {
constructor() {
this.eventSubscriptions = {};
this.dropdownMaxHeightMargin = 15;
this.menuContainer = null;
this.menuBarNode = null;
this.lastFocusedMenuBarLink = 0;
this.dropdownStack = [];
this.Tools_translate = new Tools_translate_class();
}
render_main() {
this.menuContainer = document.getElementById('main_menu');
let menuTemplate = '<ul class="menu_bar" role="menubar" tabindex="0">';
for (let i = 0; i < menuDefinition.length; i++) {
const item = menuDefinition[i];
menuTemplate += this.generate_menu_bar_item_template(item, i);
}
menuTemplate += '</ul>';
this.menuContainer.innerHTML = menuTemplate;
this.menuBarNode = this.menuContainer.querySelector('[role="menubar"]');
this.menuContainer.addEventListener('click', (event) => { return this.on_click_menu(event); }, true);
this.menuContainer.addEventListener('keydown', (event) => { return this.on_key_down_menu(event); }, true);
this.menuBarNode.addEventListener('focus', (event) => { return this.on_focus_menu_bar(event); });
this.menuBarNode.addEventListener('blur', (event) => { return this.on_blur_menu_bar(event); });
this.menuBarNode.querySelectorAll('a').forEach((link) => {
link.addEventListener('focus', (event) => { return this.on_focus_menu_bar_link(event); });
});
document.body.addEventListener('mousedown', (event) => { return this.on_mouse_down_body(event); }, true);
document.body.addEventListener('touchstart', (event) => { return this.on_mouse_down_body(event); }, true);
window.addEventListener('resize', (event) => { return this.on_resize_window(event); }, true);
document.body.classList.add('loaded');
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, this.menuContainer);
}
}
on(eventName, callback) {
if (!this.eventSubscriptions[eventName]) {
this.eventSubscriptions[eventName] = [];
}
if (!this.eventSubscriptions[eventName].includes(callback)) {
this.eventSubscriptions[eventName].push(callback);
}
}
emit(eventName, payload, object) {
if (this.eventSubscriptions[eventName]) {
for (let callback of this.eventSubscriptions[eventName]) {
callback(payload, object);
}
}
}
generate_menu_bar_item_template(definition, index) {
return `
<li>
<a id="main_menu_0_${index}" role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false"
href="javascript:void(0)" data-level="0" data-index="${ index }"><span class="name trn">${ definition.name }</span></a>
</li>
`.trim();
}
generate_menu_dropdown_item_template(definition, level, index) {
if (definition.divider) {
return `
<li role="presentation">
<hr>
</li>
`.trim();
} else {
return `
<li>
<a id="main_menu_${ level }_${ index }" role="menuitem" tabindex="-1" aria-haspopup="${ (!!definition.children) + '' }"
href="${ definition.href ? definition.href : 'javascript:void(0)' }"
target="${ definition.href ? '_blank' : '_self' }"
data-level="${ level }" data-index="${ index }">
<span class="name"><span class="trn">${ definition.name }</span>${ definition.ellipsis ? ' ...' : '' }</span>
${ !!definition.shortcut ? `
<span class="shortcut"><span class="sr_only">Shortcut Key:</span> ${ definition.shortcut }</span>
` : `` }
</a>
</li>
`.trim();
}
}
on_mouse_down_body(event) {
const target = event.touches && event.touches.length > 0 ? event.touches[0].target : event.target;
// Clicked outside of menu; close dropdowns.
if (target && !this.menuContainer.contains(target)) {
this.close_child_dropdowns(0);
}
}
on_focus_menu_bar(event) {
if (document.activeElement === this.menuBarNode) {
let lastFocusedLink = this.menuBarNode.querySelector(`[data-index="${ this.lastFocusedMenuBarLink }"]`);
if (!lastFocusedLink) {
lastFocusedLink = this.menuBarNode.querySelector('a');
}
lastFocusedLink.focus();
}
}
on_focus_menu_bar_link(event) {
this.lastFocusedMenuBarLink = parseInt(event.target.getAttribute('data-index'), 10) || 0;
}
on_blur_menu_bar(event) {
// TODO
}
on_key_down_menu(event) {
const key = event.key;
const activeElement = document.activeElement;
if (activeElement && activeElement.tagName === 'A') {
const linkLevel = parseInt(activeElement.getAttribute('data-level'), 10) || 0;
const linkIndex = parseInt(activeElement.getAttribute('data-index'), 10) || 0;
const menuParent = activeElement.closest('ul');
if (linkLevel === 0) {
if (['Right', 'ArrowRight'].includes(event.key)) {
let nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 1 }"]`);
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="0"]`);
}
nextLink.focus();
}
else if (['Left', 'ArrowLeft'].includes(event.key)) {
let previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 1 }"]`);
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ menuParent.querySelectorAll('[data-index]').length - 1 }"]`);
}
previousLink.focus();
}
else if (['Down', 'ArrowDown'].includes(event.key)) {
if (activeElement.getAttribute('aria-haspopup') === 'true') {
event.preventDefault();
activeElement.click();
}
}
else if (event.key === 'Home') {
menuParent.querySelector(`[data-index="0"]`).focus();
}
else if (event.key === 'End') {
menuParent.querySelector(`[data-index="${ menuParent.querySelectorAll('[data-index]').length - 1 }"]`).focus();
}
else if ([' ', 'Enter'].includes(event.key)) {
event.preventDefault();
activeElement.click();
}
} else {
if (['Up', 'ArrowUp'].includes(event.key)) {
event.preventDefault();
let previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 1 }"]`);
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 2 }"]`); // Skip dividers
}
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ this.dropdownStack[linkLevel - 1].children.length - 1 }"]`);
}
previousLink.focus();
}
else if (['Down', 'ArrowDown'].includes(event.key)) {
event.preventDefault();
let nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 1 }"]`);
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 2 }"]`); // Skip dividers
}
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="0"]`);
}
nextLink.focus();
}
else if (['Right', 'ArrowRight'].includes(event.key)) {
if (activeElement.getAttribute('aria-haspopup') === 'true') {
activeElement.click();
}
else if (this.dropdownStack.length > 1) {
const opener = this.dropdownStack[linkLevel - 1].opener;
opener.click();
opener.focus();
}
else {
const menuBarLinkIndex = parseInt(this.dropdownStack[0].opener.getAttribute('data-index'), 10) || 0;
let nextLink = this.menuBarNode.querySelector(`[data-index="${ menuBarLinkIndex + 1 }"]`);
if (!nextLink) {
nextLink = this.menuBarNode.querySelector(`[data-index="0"]`);
}
nextLink.click();
}
}
else if (['Left', 'ArrowLeft'].includes(event.key)) {
if (this.dropdownStack.length > 1) {
const opener = this.dropdownStack[linkLevel - 1].opener;
opener.click();
opener.focus();
} else {
const menuBarLinkIndex = parseInt(this.dropdownStack[0].opener.getAttribute('data-index'), 10) || 0;
let previousLink = this.menuBarNode.querySelector(`[data-index="${ menuBarLinkIndex - 1 }"]`);
if (!previousLink) {
previousLink = this.menuBarNode.querySelector(`[data-index="${ this.menuBarNode.querySelectorAll('[data-index]').length - 1 }"]`);
}
previousLink.click();
}
}
else if (event.key === 'Home') {
menuParent.querySelector(`[data-index="0"]`).focus();
}
else if (event.key === 'End') {
menuParent.querySelector(`[data-index="${ this.dropdownStack[linkLevel - 1].children.length - 1 }"]`).focus();
}
else if ([' ', 'Enter'].includes(event.key)) {
event.preventDefault();
activeElement.click();
}
else if (['Esc', 'Escape'].includes(event.key)) {
const opener = this.dropdownStack[linkLevel - 1].opener;
opener.click();
opener.focus();
}
else if (event.key === 'Tab') {
this.close_child_dropdowns(0);
}
}
}
}
on_click_menu(event) {
const target = event.target.closest('a');
// Any link in the menu is clicked.
if (target && target.tagName === 'A') {
const hasPopup = target.getAttribute('aria-haspopup') === 'true';
if (hasPopup) {
this.toggle_dropdown(target, event.isTrusted);
} else {
this.trigger_link(target);
}
} else {
this.close_child_dropdowns(0);
}
}
on_resize_window(event) {
if (this.dropdownStack.length > 0) {
this.position_dropdowns();
}
}
toggle_dropdown(opener, isTrusted) {
const linkLevel = parseInt(opener.getAttribute('data-level'), 10) || 0;
const linkIndex = parseInt(opener.getAttribute('data-index'), 10) || 0;
if (opener.getAttribute('aria-expanded') === 'true') {
this.close_child_dropdowns(linkLevel);
} else {
const parentList = opener.closest('ul');
parentList.querySelectorAll('a').forEach((item) => {
item.setAttribute('aria-expanded', 'false');
});
opener.setAttribute('aria-expanded', true);
this.create_dropdown(opener, linkLevel, linkIndex, !isTrusted);
}
}
trigger_link(link) {
const level = parseInt(link.getAttribute('data-level'), 10) || 0;
const index = parseInt(link.getAttribute('data-index'), 10) || 0;
// Find link definition
let children = menuDefinition;
for (let i = 0; i < level; i++) {
const childIndex = this.dropdownStack[i] != null ? this.dropdownStack[i].index : index;
children = children[childIndex].children;
}
let definition = children[index];
// Close the dropdown
this.close_child_dropdowns(0);
// Emit callback events for triggered links
if (definition.target) {
this.emit('select_target', definition.target, definition);
}
else if (definition.href) {
this.emit('select_href', definition.href, null);
}
}
close_child_dropdowns(level) {
for (let i = this.dropdownStack.length - 1; i >= 0; i--) {
if (i >= level) {
this.dropdownStack[i].element.parentNode.removeChild(this.dropdownStack[i].element);
this.dropdownStack[i].opener.setAttribute('aria-expanded', false);
}
}
this.dropdownStack = this.dropdownStack.slice(0, level);
}
create_dropdown(opener, level, index, focusAfterCreation) {
this.close_child_dropdowns(level);
// Find child list in the menu definition
let children = menuDefinition;
for (let i = 0; i <= level; i++) {
const childIndex = this.dropdownStack[i] != null ? this.dropdownStack[i].index : index;
children = children[childIndex].children;
}
// Create the dropdown element, place it in DOM & position it
let dropdownElement = document.createElement('ul');
dropdownElement.className = 'menu_dropdown';
dropdownElement.role = 'menu';
dropdownElement.tabIndex = 0;
dropdownElement.setAttribute('aria-labelledby', 'main_menu_' + level + '_' + index);
let dropdownTemplate = '';
for (let i = 0; i < children.length; i++) {
dropdownTemplate += this.generate_menu_dropdown_item_template(children[i], level + 1, i);
}
dropdownElement.innerHTML = dropdownTemplate;
this.menuContainer.appendChild(dropdownElement);
if (config.LANG != 'en') {
this.Tools_translate.translate(config.LANG, this.menuContainer);
}
if (focusAfterCreation) {
dropdownElement.querySelector('a').focus();
}
this.dropdownStack.push({
children,
opener,
index,
element: dropdownElement
});
this.position_dropdowns();
}
position_dropdowns() {
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
let topNavHeight = 0;
for (let level = 0; level < this.dropdownStack.length; level++) {
const dropdownElement = this.dropdownStack[level].element;
const openerRect = this.dropdownStack[level].opener.getBoundingClientRect();
topNavHeight = openerRect.height;
const dropdownMaxHeight = vh - topNavHeight - this.dropdownMaxHeightMargin;
dropdownElement.style.maxHeight = dropdownMaxHeight + 'px';
const dropdownRect = dropdownElement.getBoundingClientRect();
if (level === 0) {
dropdownElement.style.top = (openerRect.y + openerRect.height) + 'px';
let left = openerRect.x;
if (left + dropdownRect.width > vw) {
left = openerRect.x + openerRect.width - dropdownRect.width;
}
if (left + dropdownRect.width > vw) {
left = vw - dropdownRect.width;
}
if (left < 0) {
left = 0;
}
dropdownElement.style.left = left + 'px';
} else {
let top = openerRect.y;
if (top + dropdownRect.height > vh - this.dropdownMaxHeightMargin) {
top = vh - this.dropdownMaxHeightMargin - dropdownRect.height;
}
dropdownElement.style.top = top + 'px';
let left = openerRect.x + openerRect.width + 1;
if (left + dropdownRect.width > vw) {
left = openerRect.x - dropdownRect.width - 1;
}
if (left < 0) {
if (openerRect.x + (openerRect.width / 2) > vw / 2) {
left = 1;
} else {
left = vw - dropdownRect.width - 1;
if (left < 0) {
left = 1;
}
}
}
dropdownElement.style.left = left + 'px';
}
}
}
}
export default GUI_menu_class;
+352
View File
@@ -0,0 +1,352 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import config from './../../config.js';
import Base_layers_class from './../base-layers.js';
var instance = null;
var template = `
<div class="canvas_preview_wrapper">
<div class="transparent-grid" id="canvas_preview_background"></div>
<canvas width="176" height="100" class="transparent" id="canvas_preview"></canvas>
</div>
<div class="canvas_preview_details">
<div class="details">
<button title="Zoom out" class="layer_add trn" id="zoom_less"">-</button>
<button title="Reset zoom level" class="layer_add trn" id="zoom_100">100%</button>
<button title="Zoom in" class="layer_add trn" id="zoom_more"">+</button>
<button title="Fit window" class="layer_add trn" id="zoom_fit">Fit</button>
</div>
<input id="zoom_range" type="range" value="100" min="50" max="1000" step="50" />
</div>
`;
/**
* GUI class responsible for rendering preview on right sidebar
*/
class GUI_preview_class {
constructor(GUI_class) {
//singleton
if (instance) {
return instance;
}
instance = this;
document.getElementById('toggle_preview').innerHTML = template;
// preview mini window size on right sidebar
this.PREVIEW_SIZE = {w: 176, h: 100};
this.canvas_offset = {x: 0, y: 0};
this.zoom_data = {
x: 0,
y: 0,
move_pos: null,
};
this.mouse_pressed = false;
this.canvas_preview = null;
if (GUI_class != undefined) {
this.GUI = GUI_class;
}
this.Base_layers = new Base_layers_class();
}
render_main_preview() {
this.canvas_preview = document.getElementById("canvas_preview")
.getContext("2d");
this.prepare_canvas();
config.need_render = true;
this.set_events();
}
set_events() {
var _this = this;
var is_touch = false;
document.addEventListener('mousedown', function (e) {
_this.mouse_pressed = true;
}, false);
document.addEventListener('mouseup', function (e) {
_this.mouse_pressed = false;
}, false);
document.addEventListener('touchstart', function (e) {
_this.mouse_pressed = true;
}, false);
document.addEventListener('touchend', function (e) {
_this.mouse_pressed = false;
}, false);
document.getElementById('zoom_range').addEventListener('input', function (e) {
_this.set_center_zoom();
_this.zoom(this.value);
}, false);
document.getElementById('zoom_range').addEventListener('change', function (e) {
//IE11
if (this.value != config.ZOOM * 100) {
_this.set_center_zoom();
_this.zoom(this.value);
}
}, false);
document.getElementById('zoom_less').addEventListener('click', function (e) {
_this.set_center_zoom();
_this.zoom(-1);
}, false);
document.getElementById('zoom_100').addEventListener('click', function (e) {
_this.zoom(100);
}, false);
document.getElementById('zoom_more').addEventListener('click', function (e) {
_this.set_center_zoom();
_this.zoom(+1);
}, false);
document.getElementById('zoom_fit').addEventListener('click', function (e) {
_this.zoom_auto();
}, false);
document.getElementById('main_wrapper').addEventListener('wheel', function (e) {
//zoom with mouse scroll
e.preventDefault();
_this.zoom_data.x = e.offsetX;
_this.zoom_data.y = e.offsetY;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail || -e.deltaY)));
if (delta > 0)
_this.zoom(+1, e);
else
_this.zoom(-1, e);
}, false);
window.addEventListener('resize', function (e) {
//resize
config.need_render = true;
}, false);
document.getElementById("canvas_preview").addEventListener('mousedown', function (e) {
if(is_touch)
return;
_this.set_zoom_position(e);
}, false);
document.getElementById("canvas_preview").addEventListener('mousemove', function (e) {
if(is_touch)
return;
if (_this.mouse_pressed == false)
return;
_this.set_zoom_position(e);
}, false);
document.getElementById("canvas_preview").addEventListener('touchstart', function (e) {
is_touch = true;
//calc canvas position offset
var bodyRect = document.body.getBoundingClientRect();
var canvas_el = document.getElementById("canvas_preview").getBoundingClientRect();
_this.canvas_offset.x = canvas_el.left - bodyRect.left;
_this.canvas_offset.y = canvas_el.top - bodyRect.top;
//change zoom offset
_this.set_zoom_position(e);
});
document.getElementById("canvas_preview").addEventListener('touchmove', function (e) {
//change zoom offset
if (_this.mouse_pressed == false)
return;
_this.set_zoom_position(e);
});
}
prepare_canvas() {
this.canvas_preview.webkitImageSmoothingEnabled = false;
this.canvas_preview.msImageSmoothingEnabled = false;
this.canvas_preview.imageSmoothingEnabled = false;
this.GUI.render_canvas_background('canvas_preview', 8);
}
render_preview_active_zone() {
if (this.canvas_preview == undefined) {
this.canvas_preview = document.getElementById("canvas_preview")
.getContext("2d");
}
//active zone
var visible_w = config.visible_width / config.ZOOM;
var visible_h = config.visible_height / config.ZOOM;
var mini_rect_w = this.PREVIEW_SIZE.w * visible_w / config.WIDTH;
var mini_rect_h = this.PREVIEW_SIZE.h * visible_h / config.HEIGHT;
var start_pos = this.Base_layers.get_world_coords(0, 0);
var mini_rect_x = start_pos.x / config.WIDTH * this.PREVIEW_SIZE.w;
var mini_rect_y = start_pos.y / config.HEIGHT * this.PREVIEW_SIZE.h;
//validate
mini_rect_x = Math.max(0, mini_rect_x);
mini_rect_y = Math.max(0, mini_rect_y);
mini_rect_w = Math.min(this.PREVIEW_SIZE.w - 1, mini_rect_w);
mini_rect_h = Math.min(this.PREVIEW_SIZE.h - 1, mini_rect_h);
if (mini_rect_x + mini_rect_w > this.PREVIEW_SIZE.w)
mini_rect_x = this.PREVIEW_SIZE.w - mini_rect_w;
if (mini_rect_y + mini_rect_h > this.PREVIEW_SIZE.h)
mini_rect_y = this.PREVIEW_SIZE.h - mini_rect_h;
if (mini_rect_x == 0 && mini_rect_y == 0 && mini_rect_w == this.PREVIEW_SIZE.w - 1
&& mini_rect_h == this.PREVIEW_SIZE.h - 1) {
//everything is visible
return;
}
//draw selected area in preview canvas
this.canvas_preview.lineWidth = 1;
this.canvas_preview.beginPath();
this.canvas_preview.rect(
Math.round(mini_rect_x) + 0.5,
Math.round(mini_rect_y) + 0.5,
mini_rect_w,
mini_rect_h
);
this.canvas_preview.fillStyle = "rgba(0, 255, 0, 0.3)";
this.canvas_preview.strokeStyle = "#00ff00";
this.canvas_preview.fill();
this.canvas_preview.stroke();
}
async zoom(recalc) {
if (recalc != undefined) {
//zoom-in or zoom-out
if (recalc == 1 || recalc == -1) {
//fix
if (config.ZOOM > 1 && config.ZOOM < 1.5) {
config.ZOOM = 1;
}
if (config.ZOOM > 0.9 && config.ZOOM < 1) {
config.ZOOM = 1;
}
//calc step
if (recalc < 0) {
//down
if (config.ZOOM > 3) {
//infinity -> 300%
config.ZOOM -= 1;
}
else if (config.ZOOM > 1) {
//300% -> 100%
config.ZOOM -= 0.5;
}
else if (config.ZOOM > 0.1) {
//100% -> 10%
config.ZOOM -= 0.1;
}
else {
//10% -> 1%
config.ZOOM -= 0.01;
}
}
else {
//up
if (config.ZOOM < 0.1) {
//1% -> 10%
config.ZOOM += 0.01;
}
else if (config.ZOOM < 1) {
//10% -> 100%
config.ZOOM += 0.1;
}
else if (config.ZOOM < 3) {
//100% -> 300%
config.ZOOM += 0.5;
}
else {
//300% -> more
config.ZOOM += 1;
}
}
}
else {
//zoom using exact value
config.ZOOM = recalc / 100;
}
config.ZOOM = Math.round(config.ZOOM * 100) / 100;
config.ZOOM = Math.max(config.ZOOM, 0.01);
config.ZOOM = Math.min(config.ZOOM, 500);
}
document.getElementById("zoom_100").innerHTML = Math.round(config.ZOOM * 100) + '%';
document.getElementById("zoom_range").value = (config.ZOOM * 100);
config.need_render = true;
this.GUI.prepare_canvas();
//sleep after last image import, it maybe not be finished yet
await new Promise(r => setTimeout(r, 10));
return true;
}
zoom_auto(only_increase) {
var container = document.getElementById('main_wrapper');
var page_w = container.clientWidth;
var page_h = container.clientHeight;
var best_width = page_w / config.WIDTH;
var best_height = page_h / config.HEIGHT;
var best_zoom = null;
best_zoom = Math.min(best_width, best_height);
if (only_increase != undefined && best_zoom > 1) {
return false;
}
this.zoom(Math.min(best_width, best_height) * 100);
}
set_center_zoom() {
this.zoom_data.x = config.visible_width / 2;
this.zoom_data.y = config.visible_height / 2;
}
set_zoom_position(event) {
var mouse_x = event.offsetX;
var mouse_y = event.offsetY;
if (event.changedTouches) {
//touch events
event = event.changedTouches[0];
mouse_x = event.pageX - this.canvas_offset.x;
mouse_y = event.pageY - this.canvas_offset.y;
}
var visible_w = config.visible_width / config.ZOOM;
var visible_h = config.visible_height / config.ZOOM;
var mini_w = this.PREVIEW_SIZE.w * visible_w / config.WIDTH;
var mini_h = this.PREVIEW_SIZE.h * visible_h / config.HEIGHT;
var change_x = (mouse_x - mini_w / 2) / this.PREVIEW_SIZE.w * config.WIDTH;
var change_y = (mouse_y - mini_h / 2) / this.PREVIEW_SIZE.h * config.HEIGHT;
var zoom_data = this.zoom_data;
zoom_data.move_pos = {};
zoom_data.move_pos.x = change_x;
zoom_data.move_pos.y = change_y;
config.need_render = true;
}
/**
* moves visible area to new position.
*
* @param {int} x global offset
* @param {int} y global offset
*/
zoom_to_position(x, y) {
var zoom_data = this.zoom_data;
zoom_data.move_pos = {};
zoom_data.move_pos.x = parseInt(x);
zoom_data.move_pos.y = parseInt(y);
config.need_render = true;
}
}
export default GUI_preview_class;
+385
View File
@@ -0,0 +1,385 @@
/*
* miniPaint - https://github.com/viliusle/miniPaint
* author: Vilius L.
*/
import app from './../../app.js';
import config from './../../config.js';
import Helper_class from './../../libs/helpers.js';
import Tools_translate_class from './../../modules/tools/translate.js';
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
import Base_gui_class from '../base-gui.js';
var instance = null;
/**
* GUI class responsible for rendering left sidebar tools
*/
class GUI_tools_class {
constructor(GUI_class) {
//singleton
if (instance) {
return instance;
}
instance = this;
this.Helper = new Helper_class();
this.Tools_translate = new Tools_translate_class();
this.Base_gui = new Base_gui_class();
//active tool
this.active_tool = 'brush';
this.tools_modules = {};
}
load_plugins() {
var _this = this;
var ctx = document.getElementById('canvas_minipaint').getContext("2d");
var plugins_context = require.context("./../../tools/", true, /\.js$/);
plugins_context.keys().forEach(function (key) {
if (key.indexOf('Base' + '/') < 0) {
var moduleKey = key.replace('./', '').replace('.js', '');
var full_key = moduleKey;
if (moduleKey.indexOf('/') > -1) {
var parts = moduleKey.split("/");
moduleKey = parts[parts.length - 1];
}
var classObj = plugins_context(key);
var object = new classObj.default(ctx);
var title = _this.Helper.ucfirst(object.name);
title = title.replace(/_/, ' ');
_this.tools_modules[moduleKey] = {
key: moduleKey,
full_key: full_key,
name: object.name,
title: title,
object: object,
};
//init events once
if(typeof object.load != "undefined") {
object.load();
}
}
});
}
render_main_tools() {
this.load_plugins();
this.render_tools();
}
render_tools() {
var target_id = "tools_container";
var _this = this;
var saved_tool = this.Helper.getCookie('active_tool');
if(saved_tool == 'media' || saved_tool == 'shape') {
//bringing this back by default gives bad UX
saved_tool = null
}
if (saved_tool != null) {
this.active_tool = saved_tool;
}
//left menu
for (var i in config.TOOLS) {
var item = config.TOOLS[i];
if(item.title)
var title = item.title;
else
var title = this.Helper.ucfirst(item.name).replace(/_/, ' ');
var itemDom = document.createElement('span');
itemDom.id = item.name;
itemDom.title = title;
if (item.name == this.active_tool) {
itemDom.className = 'item trn active ' + item.name;
}
else {
itemDom.className = 'item trn ' + item.name;
}
if(item.visible === false){
itemDom.style.display = 'none';
}
//event
itemDom.addEventListener('click', function (event) {
_this.activate_tool(this.id);
});
//register
document.getElementById(target_id).appendChild(itemDom);
}
this.show_action_attributes();
new app.Actions.Activate_tool_action(this.active_tool, true).do();
this.Base_gui.check_canvas_offset();
}
async activate_tool(key) {
return app.State.do_action(
new app.Actions.Activate_tool_action(key)
);
}
action_data() {
for (var i in config.TOOLS) {
if (config.TOOLS[i].name == this.active_tool)
return config.TOOLS[i];
}
//something wrong - select first tool
this.active_tool = config.TOOLS[0].name;
return config.TOOLS[0];
}
/**
* used strings:
* "Fill", "Square", "Circle", "Radial", "Anti aliasing", "Circle", "Strict", "Burn"
*/
show_action_attributes() {
var _this = this;
var target_id = "action_attributes";
const itemContainer = document.getElementById(target_id);
itemContainer.innerHTML = "";
const attributes = this.action_data().attributes;
let itemDom;
let currentButtonGroup = null;
for (var k in attributes) {
var item = attributes[k];
var title = k[0].toUpperCase() + k.slice(1);
title = title.replace("_", " ");
if (typeof item == 'object' && typeof item.value == 'boolean' && item.icon) {
if (currentButtonGroup == null) {
currentButtonGroup = document.createElement('div');
currentButtonGroup.className = 'ui_button_group no_wrap';
itemDom = document.createElement('div');
itemDom.className = 'item ' + k;
itemContainer.appendChild(itemDom);
itemDom.appendChild(currentButtonGroup);
} else {
itemDom.classList.add(k);
}
} else {
itemDom = document.createElement('div');
itemDom.className = 'item ' + k;
itemContainer.appendChild(itemDom);
currentButtonGroup = null;
}
if (typeof item == 'boolean' || (typeof item == 'object' && typeof item.value == 'boolean')) {
//boolean - true, false
let value = item;
let icon = null;
if (typeof item == 'object') {
value = item.value;
if (item.icon) {
icon = item.icon;
}
}
const element = document.createElement('button');
element.className = 'trn';
element.type = 'button';
element.id = k;
element.innerHTML = title;
element.setAttribute('aria-pressed', value);
if (icon) {
element.classList.add('ui_icon_button');
element.classList.add('input_height');
element.innerHTML = icon;
element.title = k;
element.innerHTML = '<img style="width:16px;height:16px;" alt="'+title+'" src="images/icons/'+icon+'" />';
} else {
element.classList.add('ui_toggle_button');
}
//event
element.addEventListener('click', (event) => {
//toggle boolean
var new_value = element.getAttribute('aria-pressed') !== 'true';
const actionData = this.action_data();
const attributes = actionData.attributes;
const id = event.target.closest('button').id;
if (typeof attributes[id] === 'object') {
attributes[id].value = new_value;
} else {
attributes[id] = new_value;
}
element.setAttribute('aria-pressed', new_value);
if (actionData.on_update != undefined) {
//send event
var moduleKey = actionData.name;
var functionName = actionData.on_update;
this.tools_modules[moduleKey].object[functionName]({ key: id, value: new_value });
}
});
if (currentButtonGroup) {
currentButtonGroup.appendChild(element);
} else {
itemDom.appendChild(element);
}
}
else if (typeof item == 'number' || (typeof item == 'object' && typeof item.value == 'number')) {
//numbers
let min = 1;
let max = k === 'power' ? 100 : 999;
let value = item;
let step = null;
if (typeof item == 'object') {
value = item.value;
if (item.min != null) {
min = item.min;
}
if (item.max != null) {
max = item.max;
}
if (item.step != null) {
step = item.step;
}
}
var elementTitle = document.createElement('label');
elementTitle.innerHTML = title + ':';
elementTitle.id = 'attribute_label_' + k;
elementTitle.className = 'trn';
const elementInput = document.createElement('input');
elementInput.type = 'number';
elementInput.setAttribute('aria-labelledby', 'attribute_label_' + k);
const $numberInput = $(elementInput)
.uiNumberInput({
id: k,
min,
max,
value,
step: step || 1,
exponentialStepButtons: !step
})
.on('input', () => {
let value = $numberInput.uiNumberInput('get_value');
const id = $numberInput.uiNumberInput('get_id');
const actionData = this.action_data();
const attributes = actionData.attributes;
if (typeof attributes[id] === 'object') {
attributes[id].value = value;
} else {
attributes[id] = value;
}
if (actionData.on_update != undefined) {
//send event
var moduleKey = actionData.name;
var functionName = actionData.on_update;
this.tools_modules[moduleKey].object[functionName]({ key: id, value: value });
}
});
itemDom.appendChild(elementTitle);
itemDom.appendChild($numberInput[0]);
}
else if (typeof item == 'object') {
//select
var elementTitle = document.createElement('label');
elementTitle.innerHTML = title + ':';
elementTitle.for = k;
elementTitle.className = 'trn';
var selectList = document.createElement("select");
selectList.id = k;
const values = typeof item.values === 'function' ? item.values() : item.values;
for (let j in values) {
var option = document.createElement("option");
if (item.value == values[j]) {
option.selected = 'selected';
}
option.className = 'trn';
option.name = values[j];
option.value = values[j];
option.text = values[j];
selectList.appendChild(option);
}
//event
selectList.addEventListener('change', (event) => {
const actionData = this.action_data();
actionData.attributes[event.target.id].value = event.target.value;
if (actionData.on_update != undefined) {
//send event
var moduleKey = actionData.name;
var functionName = actionData.on_update;
const result = this.tools_modules[moduleKey].object[functionName]({ key: event.target.id, value: event.target.value });
if (result) {
// Allow the on_update function to modify the attribute value if necessary.
if (result.new_values) {
for (let key in result.new_values) {
actionData.attributes[key].value = result.new_values[key];
}
}
}
}
this.show_action_attributes();
});
itemDom.appendChild(elementTitle);
itemDom.appendChild(selectList);
}
else if (typeof item == 'string' && item[0] == '#') {
//color
var elementTitle = document.createElement('label');
elementTitle.innerHTML = title + ':';
elementTitle.for = k;
elementTitle.className = 'trn';
var colorInput = document.createElement('input');
colorInput.type = 'color';
const $colorInput = $(colorInput)
.uiColorInput({
id: k,
value: item
})
.on('change', () => {
let value = $colorInput.uiColorInput('get_value');
const id = $colorInput.uiColorInput('get_id');
const actionData = this.action_data();
actionData.attributes[id] = value;
if (actionData.on_update != undefined) {
//send event
var moduleKey = actionData.name;
var functionName = actionData.on_update;
this.tools_modules[moduleKey].object[functionName]({ key: id, value: value });
}
});
itemDom.appendChild(elementTitle);
itemDom.appendChild($colorInput[0]);
}
else {
alertify.error('Error: unsupported attribute type:' + typeof item + ', ' + k);
}
}
if (config.LANG != 'en') {
//retranslate
this.Tools_translate.translate(config.LANG);
}
}
}
export default GUI_tools_class;