Add miniPaint as new frontend base
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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"> </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;
|
||||
@@ -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;
|
||||
@@ -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">↓</button>
|
||||
<button type="button" class="layers_arrow trn" title="Move layer up" id="layer_up">↑</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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user