diff --git a/examples/open-edit-save.html b/examples/open-edit-save.html index 8ec8d55..9645309 100644 --- a/examples/open-edit-save.html +++ b/examples/open-edit-save.html @@ -72,13 +72,11 @@ function save_image(){ //update image using blob (faster) tempCanvas.toBlob(function (blob) { alert('Data length: ' + blob.size); - console.log(blob); }, 'image/png'); } else{ //slow way for IE, Edge var data = tempCanvas.toDataURL(); - console.log(data); alert('Data length: ' + data.length); } } diff --git a/src/js/actions/add-layer-filter.js b/src/js/actions/add-layer-filter.js new file mode 100644 index 0000000..cbdc134 --- /dev/null +++ b/src/js/actions/add-layer-filter.js @@ -0,0 +1,55 @@ +import app from './../app.js'; +import config from './../config.js'; +import { Base_action } from './base.js'; + +export class Add_layer_filter_action extends Base_action { + /** + * register new live filter + * + * @param {int} layer_id + * @param {string} name + * @param {object} params + */ + constructor(layer_id, name, params) { + super('add_layer_filter', 'Add Layer Filter'); + if (layer_id == null) + layer_id = config.layer.id; + this.layer_id = parseInt(layer_id); + this.filter_id = Math.floor(Math.random() * 999999999) + 1; // A good UUID library would + this.name = name; + this.params = params; + this.reference_layer = null; + } + + async do() { + super.do(); + this.reference_layer = app.Layers.get_layer(this.layer_id); + if (!this.reference_layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + var filter = { + id: this.filter_id, + name: this.name, + params: this.params, + }; + this.reference_layer.filters.push(filter); + + config.need_render = true; + app.GUI.GUI_layers.render_layers(); + } + + async undo() { + super.undo(); + if (this.reference_layer) { + this.reference_layer.filters.pop(); + this.reference_layer = null; + } + config.need_render = true; + app.GUI.GUI_layers.render_layers(); + } + + free() { + this.reference_layer = null; + this.params = null; + } +} \ No newline at end of file diff --git a/src/js/actions/base.js b/src/js/actions/base.js index c025caa..11da929 100644 --- a/src/js/actions/base.js +++ b/src/js/actions/base.js @@ -4,12 +4,14 @@ export class Base_action { this.action_id = action_id; this.action_description = action_description; this.is_done = false; + this.memory_estimate = 0; // Estimate of how much memory will be freed when the free() method is called (in bytes) } do() { this.is_done = true; } undo() { this.is_done = false; + this.memory_estimate = 0; } free() { // Override if need to run tasks to free memory when action is discarded from history diff --git a/src/js/actions/bundle.js b/src/js/actions/bundle.js index 9bf57e3..b2635d0 100644 --- a/src/js/actions/bundle.js +++ b/src/js/actions/bundle.js @@ -41,6 +41,11 @@ export class Bundle_action extends Base_action { } free() { - this.actions_to_do = null; + if (this.actions_to_do) { + for (let action of this.actions_to_do) { + action.free(); + } + this.actions_to_do = null; + } } } \ No newline at end of file diff --git a/src/js/actions/clear-layer.js b/src/js/actions/clear-layer.js new file mode 100644 index 0000000..a9f8d5a --- /dev/null +++ b/src/js/actions/clear-layer.js @@ -0,0 +1,82 @@ +import app from './../app.js'; +import config from './../config.js'; +import { Base_action } from './base.js'; + +export class Clear_layer_action extends Base_action { + /** + * clear layer data + * + * @param {int} layer_id + */ + constructor(layer_id) { + super('clear_layer', 'Clear Layer'); + this.layer_id = parseInt(layer_id); + this.update_layer_action = null; + this.delete_layer_settings_action = null; + } + + async do() { + super.do(); + let layer = app.Layers.get_layer(this.layer_id); + if (!layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + let new_settings = { + x: 0, + y: 0, + width: 0, + height: 0, + visible: true, + opacity: 100, + composition: null, + rotate: 0, + data: null, + params: {}, + status: null, + render_function: null, + type: null + }; + if (layer.type == 'image') { + //clean image + new_settings.link = null; + } + this.update_layer_action = new app.Actions.Update_layer_action(this.layer_id, new_settings); + await this.update_layer_action.do(); + let delete_setting_names = []; + for (let prop_name in layer) { + //remove private attributes + if (prop_name[0] == '_') { + delete_setting_names.push(prop_name); + } + } + if (delete_setting_names.length > 0) { + this.delete_layer_settings_action = new app.Actions.Delete_layer_settings_action(this.layer_id, delete_setting_names); + await this.delete_layer_settings_action.do(); + } + } + + async undo() { + super.undo(); + if (this.delete_layer_settings_action) { + await this.delete_layer_settings_action.undo(); + this.delete_layer_settings_action.free(); + this.delete_layer_settings_action = null; + } + if (this.update_layer_action) { + await this.update_layer_action.undo(); + this.update_layer_action.free(); + this.update_layer_action = null; + } + } + + free() { + if (this.update_layer_action) { + this.update_layer_action.free(); + this.update_layer_action = null; + } + if (this.delete_layer_settings_action) { + this.delete_layer_settings_action.free(); + this.delete_layer_settings_action = null; + } + } +} \ No newline at end of file diff --git a/src/js/actions/delete-layer-filter.js b/src/js/actions/delete-layer-filter.js new file mode 100644 index 0000000..821f233 --- /dev/null +++ b/src/js/actions/delete-layer-filter.js @@ -0,0 +1,60 @@ +import app from './../app.js'; +import config from './../config.js'; +import { Base_action } from './base.js'; + +export class Delete_layer_filter_action extends Base_action { + /** + * delete live filter + * + * @param {int} layer_id + * @param {string} filter_id + */ + constructor(layer_id, filter_id) { + super('delete_layer_filter', 'Delete Layer Filter'); + if (layer_id == null) + layer_id = config.layer.id; + this.layer_id = parseInt(layer_id); + this.filter_id = filter_id; + this.reference_layer = null; + this.filter_remove_index = null; + this.old_filter = null; + } + + async do() { + super.do(); + this.reference_layer = app.Layers.get_layer(this.layer_id); + if (!this.reference_layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + this.old_filter = null; + for (let i in this.reference_layer.filters) { + if (this.reference_layer.filters[i].id == this.filter_id) { + this.filter_remove_index = i; + this.old_filter = this.reference_layer.filters.splice(i, 1)[0]; + break; + } + } + if (!this.old_filter) { + throw new Error('Aborted - filter with specified id doesn\'t exist in layer'); + } + config.need_render = true; + app.GUI.GUI_layers.render_layers(); + } + + async undo() { + super.undo(); + if (this.reference_layer && this.old_filter) { + this.reference_layer.filters.splice(this.filter_remove_index, 0, this.old_filter); + } + this.reference_layer = null; + this.old_filter = null; + this.filter_remove_index = null; + config.need_render = true; + app.GUI.GUI_layers.render_layers(); + } + + free() { + this.reference_layer = null; + this.old_filter = null; + } +} \ No newline at end of file diff --git a/src/js/actions/delete-layer-settings.js b/src/js/actions/delete-layer-settings.js new file mode 100644 index 0000000..8587fb0 --- /dev/null +++ b/src/js/actions/delete-layer-settings.js @@ -0,0 +1,50 @@ +import app from './../app.js'; +import config from './../config.js'; +import { Base_action } from './base.js'; + +export class Delete_layer_settings_action extends Base_action { + /** + * Deletes the specified settings in a layer + * + * @param {int} layer_id + * @param {array} setting_names + */ + constructor(layer_id, setting_names) { + super('delete_layer_settings', 'Delete Layer Settings'); + this.layer_id = parseInt(layer_id); + this.setting_names = setting_names; + this.reference_layer = null; + this.old_settings = {}; + } + + async do() { + super.do(); + this.reference_layer = app.Layers.get_layer(this.layer_id); + if (!this.reference_layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + for (let name in this.setting_names) { + this.old_settings[name] = this.reference_layer[name]; + delete this.reference_layer[name]; + } + config.need_render = true; + } + + async undo() { + super.undo(); + if (this.reference_layer) { + for (let i in this.old_settings) { + this.reference_layer[i] = this.old_settings[i]; + } + this.old_settings = {}; + } + this.reference_layer = null; + config.need_render = true; + } + + free() { + this.setting_names = null; + this.reference_layer = null; + this.old_settings = null; + } +} \ No newline at end of file diff --git a/src/js/actions/delete-layer.js b/src/js/actions/delete-layer.js index 889d4a9..5d6a6af 100644 --- a/src/js/actions/delete-layer.js +++ b/src/js/actions/delete-layer.js @@ -76,10 +76,12 @@ export class Delete_layer_action extends Base_action { } if (this.select_layer_action) { await this.select_layer_action.undo(); + this.select_layer_action.free(); this.select_layer_action = null; } if (this.insert_layer_action) { await this.insert_layer_action.undo(); + this.insert_layer_action.free(); this.insert_layer_action = null; } @@ -91,8 +93,14 @@ export class Delete_layer_action extends Base_action { if (this.deleted_layer) { delete this.deleted_layer.link; } - this.insert_layer_action = null; - this.select_layer_action = null; + if (this.insert_layer_action) { + this.insert_layer_action.free(); + this.insert_layer_action = null; + } + if (this.select_layer_action) { + this.select_layer_action.free(); + this.select_layer_action = null; + } this.deleted_layer = null; } } \ No newline at end of file diff --git a/src/js/actions/index.js b/src/js/actions/index.js index 30d2bc3..284504a 100644 --- a/src/js/actions/index.js +++ b/src/js/actions/index.js @@ -1,14 +1,21 @@ +export { Add_layer_filter_action } from './add-layer-filter.js'; export { Autoresize_canvas_action } from './autoresize-canvas.js'; export { Bundle_action } from './bundle.js'; +export { Clear_layer_action } from './clear-layer.js'; export { Delete_layer_action } from './delete-layer.js'; +export { Delete_layer_filter_action } from './delete-layer-filter.js'; +export { Delete_layer_settings_action } from './delete-layer-settings.js'; export { Init_canvas_zoom_action } from './init-canvas-zoom.js'; export { Insert_layer_action } from './insert-layer.js'; export { Prepare_canvas_action } from './prepare-canvas.js'; +export { Reorder_layer_action } from './reorder-layer.js'; export { Reset_layers_action } from './reset-layers.js'; export { Reset_selection_action } from './reset-selection.js'; export { Select_layer_action } from './select-layer.js'; export { Select_next_layer_action } from './select-next-layer.js'; export { Select_previous_layer_action } from './select-previous-layer.js'; +export { Set_selection_action } from './set-selection.js'; export { Toggle_layer_visibility_action } from './toggle-layer-visibility.js'; export { Update_config_action } from './update-config.js'; +export { Update_layer_image_action } from './update-layer-image.js'; export { Update_layer_action } from './update-layer.js'; \ No newline at end of file diff --git a/src/js/actions/insert-layer.js b/src/js/actions/insert-layer.js index b1d6f96..dc305e7 100644 --- a/src/js/actions/insert-layer.js +++ b/src/js/actions/insert-layer.js @@ -1,6 +1,7 @@ import app from './../app.js'; import config from './../config.js'; import { Base_action } from './base.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; export class Insert_layer_action extends Base_action { /** @@ -23,6 +24,7 @@ export class Insert_layer_action extends Base_action { async do() { super.do(); + this.previous_auto_increment = app.Layers.auto_increment; this.previous_selected_layer = config.layer; let autoresize_as = null; @@ -78,7 +80,7 @@ export class Insert_layer_action extends Base_action { // Remove first empty layer this.delete_layer_action = new app.Actions.Delete_layer_action(config.layer.id, true); - this.delete_layer_action.do(); + await this.delete_layer_action.do(); } if (layer.link == null) { @@ -179,15 +181,17 @@ export class Insert_layer_action extends Base_action { this.autoresize_canvas_action = null; } if (this.inserted_layer_id) { - await new app.Actions.Delete_layer_action(this.inserted_layer_id, true).do(); + config.layers.pop(); this.inserted_layer_id = null; } if (this.update_layer_action) { await this.update_layer_action.undo(); + this.update_layer_action.free(); this.update_layer_action = null; } if (this.delete_layer_action) { await this.delete_layer_action.undo(); + this.delete_layer_action.free(); this.delete_layer_action = null; } config.layer = this.previous_selected_layer; @@ -198,8 +202,14 @@ export class Insert_layer_action extends Base_action { } free() { - this.delete_layer_action = null; - this.update_layer_action = null; + if (this.delete_layer_action) { + this.delete_layer_action.free(); + this.delete_layer_action = null; + } + if (this.update_layer_action) { + this.update_layer_action.free(); + this.update_layer_action = null; + } this.previous_selected_layer = null; } } \ No newline at end of file diff --git a/src/js/actions/reorder-layer.js b/src/js/actions/reorder-layer.js new file mode 100644 index 0000000..8225a99 --- /dev/null +++ b/src/js/actions/reorder-layer.js @@ -0,0 +1,64 @@ +import app from '../app.js'; +import config from '../config.js'; +import { Base_action } from './base.js'; + +export class Reorder_layer_action extends Base_action { + /** + * Reorder layer up or down in the layer stack + * + * @param {int} layer_id + * @param {int} direction + */ + constructor(layer_id, direction) { + super('reorder_layer', 'Reorder Layer'); + this.layer_id = parseInt(layer_id); + this.direction = direction; + this.reference_layer = null; + this.reference_target = null; + this.old_layer_order = null; + this.old_target_order = null; + } + + async do() { + super.do(); + this.reference_layer = app.Layers.get_layer(this.layer_id); + if (!this.reference_layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + if (this.direction < 0) { + this.reference_target = app.Layers.find_previous(this.layer_id); + } + else { + this.reference_target = app.Layers.find_next(this.layer_id); + } + if (!this.reference_target) { + throw new Error('Aborted - layer has nowhere to move'); + } + this.old_layer_order = this.reference_layer.order; + this.old_target_order = this.reference_target.order; + this.reference_layer.order = this.old_target_order; + this.reference_target.order = this.old_layer_order; + + app.Layers.render(); + app.GUI.GUI_layers.render_layers(); + } + + async undo() { + super.undo(); + if (this.reference_layer) { + this.reference_layer.order = this.old_layer_order; + this.reference_layer = null; + } + if (this.reference_target) { + this.reference_target.order = this.old_target_order; + this.reference_target = null; + } + app.Layers.render(); + app.GUI.GUI_layers.render_layers(); + } + + free() { + this.reference_layer = null; + this.reference_target = null; + } +} \ No newline at end of file diff --git a/src/js/actions/reset-layers.js b/src/js/actions/reset-layers.js index b89a425..ea289fc 100644 --- a/src/js/actions/reset-layers.js +++ b/src/js/actions/reset-layers.js @@ -39,15 +39,28 @@ export class Reset_layers_action extends Base_action { super.undo(); if (this.insert_action) { await this.insert_action.undo(); + this.insert_action.free(); this.insert_action = null; } for (let i = this.delete_actions.length - 1; i >= 0; i--) { await this.delete_actions[i].undo(); + this.delete_actions[i].free(); } app.Layers.auto_increment = this.previous_auto_increment; app.Layers.render(); app.GUI.GUI_layers.render_layers(); } - + free() { + if (this.insert_action) { + this.insert_action.free(); + this.insert_action = null; + } + if (this.delete_actions) { + for (let action of this.delete_actions) { + action.free(); + } + this.delete_actions = null; + } + } } \ No newline at end of file diff --git a/src/js/actions/reset-selection.js b/src/js/actions/reset-selection.js index a614185..7f775ce 100644 --- a/src/js/actions/reset-selection.js +++ b/src/js/actions/reset-selection.js @@ -15,19 +15,23 @@ export class Reset_selection_action extends Base_action { async do() { super.do(); this.settings_reference = app.Layers.Base_selection.find_settings(); - this.old_settings_data = this.settings_reference.data; - this.settings_reference.data = { - x: null, - y: null, - width: null, - height: null - }; + this.old_settings_data = JSON.parse(JSON.stringify(this.settings_reference.data)); + this.settings_reference.data = { + x: null, + y: null, + width: null, + height: null + } config.need_render = true; } async undo() { super.undo(); - this.settings_reference.data = this.old_settings_data; + if (this.old_settings_data) { + for (let prop in this.old_settings_data) { + this.settings_reference.data[prop] = this.old_settings_data[prop]; + } + } this.settings_reference = null; this.old_settings_data = null; config.need_render = true; diff --git a/src/js/actions/select-layer.js b/src/js/actions/select-layer.js index 93e1f5b..b27c97d 100644 --- a/src/js/actions/select-layer.js +++ b/src/js/actions/select-layer.js @@ -41,6 +41,7 @@ export class Select_layer_action extends Base_action { if (this.reset_selection_action) { await this.reset_selection_action.undo(); + this.reset_selection_action.free(); this.reset_selection_action = null; } @@ -53,5 +54,9 @@ export class Select_layer_action extends Base_action { free() { this.old_layer = null; + if (this.reset_selection_action) { + this.reset_selection_action.free(); + this.reset_selection_action = null; + } } } \ No newline at end of file diff --git a/src/js/actions/set-selection.js b/src/js/actions/set-selection.js new file mode 100644 index 0000000..c6255dc --- /dev/null +++ b/src/js/actions/set-selection.js @@ -0,0 +1,57 @@ +import app from '../app.js'; +import config from '../config.js'; +import { Base_action } from './base.js'; + +export class Set_selection_action extends Base_action { + /** + * Sets the selection to the specified position and dimensions + */ + constructor(x, y, width, height, old_settings_override) { + super('set_selection', 'Set Selection'); + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.settings_reference = null; + this.old_settings_data = null; + this.old_settings_override = old_settings_override || null; + } + + async do() { + super.do(); + this.settings_reference = app.Layers.Base_selection.find_settings(); + this.old_settings_data = JSON.parse(JSON.stringify(this.settings_reference.data)); + if (this.x != null) + this.settings_reference.data.x = this.x; + if (this.y != null) + this.settings_reference.data.y = this.y; + if (this.width != null) + this.settings_reference.data.width = this.width; + if (this.height != null) + this.settings_reference.data.height = this.height; + + config.need_render = true; + } + + async undo() { + super.undo(); + if (this.old_settings_override) { + for (let prop in this.old_settings_override) { + this.settings_reference.data[prop] = this.old_settings_override[prop]; + } + } else { + for (let prop in this.old_settings_data) { + this.settings_reference.data[prop] = this.old_settings_data[prop]; + } + } + this.settings_reference = null; + this.old_settings_data = null; + config.need_render = true; + } + + free() { + this.settings_reference = null; + this.old_settings_override = null; + this.old_settings_data = null; + } +} \ No newline at end of file diff --git a/src/js/actions/store/image-store.js b/src/js/actions/store/image-store.js new file mode 100644 index 0000000..0d8f133 --- /dev/null +++ b/src/js/actions/store/image-store.js @@ -0,0 +1,143 @@ +import { get } from "jquery"; + +let idCounter = 0; +let database = null; +let databaseInitPromise = null; + +export default { + /** + * Initializes the database + */ + async init() { + if (!databaseInitPromise) { + databaseInitPromise = new Promise(async (resolveInit) => { + try { + if (window.indexedDB) { + // Delete database from a previous page load + await new Promise((resolve, reject) => { + let deleteRequest = window.indexedDB.deleteDatabase('undoHistoryImageStore'); + deleteRequest.onerror = () => { + reject(deleteRequest.error); + }; + deleteRequest.onsuccess = () => { + resolve(); + }; + }); + // Initialize database + await new Promise((resolve, reject) => { + let openRequest = window.indexedDB.open('undoHistoryImageStore', 1); + openRequest.onupgradeneeded = function(event) { + database = openRequest.result; + switch (event.oldVersion) { + case 0: + database.createObjectStore('images', { keyPath: 'id' }); + break; + } + }; + openRequest.onerror = () => { + reject(openRequest.error); + } + openRequest.onsuccess = () => { + resolve(); + database = openRequest.result; + } + }); + if (!database) { + throw new Error('indexedDB not initialized'); + } + } + } catch (error) { + database = { + isMemory: true, + images: {} + }; + } + resolveInit(); + }); + await databaseInitPromise; + } else if (!database) { + await databaseInitPromise; + } + }, + + /** + * Adds the specified image to the database. Returns a promise that is resolved with an id that can be used to retrieve it again. + * + * @param {string | canvas | ImageData} imageData the image data to store + * @returns {Promise} resolves with retrieval id + */ + async add(imageData) { + await this.init(); + let imageId = (idCounter++) + ''; + if (database.isMemory) { + database.images[imageId] = imageData; + } else { + await new Promise((resolve, reject) => { + const transaction = database.transaction('images', 'readwrite'); + const images = transaction.objectStore('images'); + const image = { + id: imageId, + data: imageData + } + const request = images.add(image); + request.onsuccess = function() { + resolve(); + }; + request.onerror = function() { + reject(request.error); + }; + }); + } + return imageId; + }, + + /** + * Gets the specified image from the database, by imageId retrieved from "add()" method. + * + * @param {string} imageId the id of the image to get + * @returns {Promise} resolves with the image + */ + async get(imageId) { + await this.init(); + if (database.isMemory) { + return database.images[imageId]; + } else { + return new Promise((resolve, reject) => { + const transaction = database.transaction('images', 'readonly'); + const images = transaction.objectStore('images'); + const request = images.get(imageId); + request.onsuccess = function() { + resolve(request.result.data); + }; + request.onerror = function() { + reject(request.error); + }; + }); + } + }, + + /** + * Deletes the specified image from the database, by imageId retrieved from "add()" method. + * + * @param {string} imageId the id of the image to delete + * @returns {Promise} resolves with the image + */ + async delete(imageId) { + await this.init(); + if (database.isMemory) { + delete database.images[imageId]; + } else { + return new Promise((resolve, reject) => { + const transaction = database.transaction('images', 'readwrite'); + const images = transaction.objectStore('images'); + const request = images.delete(imageId); + request.onsuccess = function() { + resolve(); + }; + request.onerror = function() { + reject(request.error); + }; + }); + } + } +}; \ No newline at end of file diff --git a/src/js/actions/update-layer-image.js b/src/js/actions/update-layer-image.js new file mode 100644 index 0000000..64fc46e --- /dev/null +++ b/src/js/actions/update-layer-image.js @@ -0,0 +1,98 @@ +import app from './../app.js'; +import config from './../config.js'; +import Helper_class from './../libs/helpers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; +import image_store from './store/image-store.js'; +import { Base_action } from './base.js'; + +const Helper = new Helper_class(); + +export class Update_layer_image_action extends Base_action { + /** + * updates layer image data + * + * @param {canvas} canvas + * @param {int} layer_id (optional) + */ + constructor(canvas, layer_id) { + super('update_layer_image', 'Update Layer Image'); + this.canvas = canvas; + this.canvas_data_url = null; + if (layer_id == null) + layer_id = config.layer.id; + this.layer_id = parseInt(layer_id); + this.reference_layer = null; + this.old_image_id = null; + } + + async do() { + super.do(); + this.reference_layer = app.Layers.get_layer(this.layer_id); + if (!this.reference_layer) { + throw new Error('Aborted - layer with specified id doesn\'t exist'); + } + if (this.reference_layer.type != 'image'){ + alertify.error('Error: layer must be image.'); + throw new Error('Aborted - layer is not an image'); + } + + if (!this.canvas_data_url) { + if (Helper.is_edge_or_ie() == false) { + // Update image using blob (faster) + await new Promise((resolve) => { + this.canvas.toBlob((blob) => { + this.canvas_data_url = window.URL.createObjectURL(blob); + resolve(); + }, 'image/png'); + }); + } + else { + // Slow way for IE, Edge + this.canvas_data_url = this.canvas.toDataURL(); + } + this.canvas = null; + } + + try { + this.old_image_id = await image_store.add(this.reference_layer.link.src); + } catch (error) { + console.log(error); + // TODO - need to delete undo history, how to handle this? + } + this.reference_layer.link.src = this.canvas_data_url; + + config.need_render = true; + } + + async undo() { + super.undo(); + if (this.old_image_id != null) { + try { + this.reference_layer.link.src = await image_store.get(this.old_image_id); + } catch (error) { + throw new Error('Failed to retrieve image from store'); + } + try { + await image_store.delete(this.old_image_id); + } catch (error) { + // TODO - Reduce assumed total memory storage by image size + } + this.old_image_id = null; + } + this.reference_layer = null; + config.need_render = true; + } + + async free() { + if (this.old_image_id != null) { + try { + await image_store.delete(this.old_image_id); + } catch (error) { + // TODO - Reduce assumed total memory storage by image size + } + this.old_image_id = null; + } + this.reference_layer = null; + this.canvas_data_url = null; + } +} \ No newline at end of file diff --git a/src/js/actions/update-layer.js b/src/js/actions/update-layer.js index 0a729e0..894133d 100644 --- a/src/js/actions/update-layer.js +++ b/src/js/actions/update-layer.js @@ -5,6 +5,7 @@ import { Base_action } from './base.js'; export class Update_layer_action extends Base_action { /** * Updates an existing layer with the provided settings + * WARNING: If passing objects or arrays into settings, make sure these are new objects, and not a modified existing object! * * @param {string} layer_id * @param {object} settings @@ -20,11 +21,9 @@ export class Update_layer_action extends Base_action { async do() { super.do(); this.reference_layer = app.Layers.get_layer(this.layer_id); - if (!this.reference_layer) { throw new Error('Aborted - layer with specified id doesn\'t exist'); } - for (let i in this.settings) { if (i == 'id') continue; @@ -33,6 +32,10 @@ export class Update_layer_action extends Base_action { this.old_settings[i] = this.reference_layer[i]; this.reference_layer[i] = this.settings[i]; } + if (this.settings.data && this.reference_layer.type === 'text') { + this.reference_layer._needs_update_data = true; + } + config.need_render = true; } async undo() { @@ -41,9 +44,13 @@ export class Update_layer_action extends Base_action { for (let i in this.old_settings) { this.reference_layer[i] = this.old_settings[i]; } + if (this.old_settings.data && this.reference_layer[i] === 'text') { + this.reference_layer._needs_update_data = true; + } this.old_settings = {}; } this.reference_layer = null; + config.need_render = true; } free() { diff --git a/src/js/core/base-layers.js b/src/js/core/base-layers.js index 65154e6..4fc3813 100644 --- a/src/js/core/base-layers.js +++ b/src/js/core/base-layers.js @@ -411,16 +411,15 @@ class Base_layers_class { * @param {int} id * @param {int} value 0-100 */ - set_opacity(id, value) { - id = parseInt(id); + async set_opacity(id, value) { value = parseInt(value); if (value < 0 || value > 100) { //reset value = 100; } - var link = this.get_layer(id); - - link.opacity = value; + return app.State.do_action( + new app.Actions.Update_layer_action(id, { opacity: value }) + ); } /** @@ -428,36 +427,10 @@ class Base_layers_class { * * @param {int} id */ - layer_clear(id) { - id = parseInt(id); - var link = this.get_layer(id); - - if (link.type == 'image') { - //clean image - link.link = null; - } - - for (var i in link) { - //remove private attributes - if (i[0] == '_') - delete link[i]; - } - - link.x = 0; - link.y = 0; - link.width = 0; - link.height = 0; - link.visible = true; - link.opacity = 100; - link.composition = null; - link.rotate = 0; - link.data = null; - link.params = {}; - link.status = null; - link.render_function = null; - link.type = null; - - config.need_render = true; + async layer_clear(id) { + return app.State.do_action( + new app.Actions.Clear_layer_action(id) + ); } /** @@ -466,24 +439,10 @@ class Base_layers_class { * @param {int} id * @param {int} direction */ - move(id, direction) { - id = parseInt(id); - var link = this.get_layer(id); - - if (direction < 0) { - var target = this.find_previous(id); - } - else { - var target = this.find_next(id); - } - if (target != null) { - var current_order = link.order; - link.order = target.order; - target.order = current_order; - } - - this.render(); - this.Base_gui.GUI_layers.render_layers(); + async move(id, direction) { + return app.State.do_action( + new app.Actions.Reorder_layer_action(id, direction) + ); } /** @@ -579,18 +538,9 @@ class Base_layers_class { * @param {object} params */ add_filter(layer_id, name, params) { - if (layer_id == null) - layer_id = config.layer.id; - var link = this.get_layer(layer_id); - var filter = { - id: this.Helper.getRandomInt(1, 999999999), - name: name, - params: params, - }; - link.filters.push(filter); - - config.need_render = true; - this.Base_gui.GUI_layers.render_layers(); + return app.State.do_action( + new app.Actions.Add_layer_filter_action(layer_id, name, params) + ); } /** @@ -600,18 +550,9 @@ class Base_layers_class { * @param {string} filter_id */ delete_filter(layer_id, filter_id) { - if (layer_id == null) - layer_id = config.layer.id; - var link = this.get_layer(layer_id); - - for (var i in link.filters) { - if (link.filters[i].id == filter_id) { - link.filters.splice(i, 1); - } - } - - config.need_render = true; - this.Base_gui.GUI_layers.render_layers(); + return app.State.do_action( + new app.Actions.Delete_layer_filter_action(layer_id, filter_id) + ); } /** @@ -704,28 +645,9 @@ class Base_layers_class { * @param {int} layer_id (optional) */ update_layer_image(canvas, layer_id) { - if (layer_id == null) - layer_id = config.layer.id; - var link = this.get_layer(layer_id); - - if (link.type != 'image'){ - alertify.error('Error: layer must be image.'); - return null; - } - - if(this.Helper.is_edge_or_ie() == false){ - //update image using blob (faster) - canvas.toBlob(function (blob) { - link.link.src = window.URL.createObjectURL(blob); - config.need_render = true; - }, 'image/png'); - } - else{ - //slow way for IE, Edge - link.link.src = canvas.toDataURL(); - } - - config.need_render = true; + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas, layer_id) + ); } /** diff --git a/src/js/core/base-selection.js b/src/js/core/base-selection.js index e16bffc..5b4aa9e 100644 --- a/src/js/core/base-selection.js +++ b/src/js/core/base-selection.js @@ -45,39 +45,47 @@ class Base_selection_class { 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.events(); } events() { - var _this = this; - - document.addEventListener('mousedown', function (e) { - if(_this.is_touch == true) + document.addEventListener('mousedown', (e) => { + this.is_drag = false; + if(this.is_touch == true) return; - _this.selected_object_actions(e); + if (!e.target.closest('#main_wrapper')) + return; + this.is_drag = true; + this.selected_object_actions(e); }); - document.addEventListener('mousemove', function (e) { - if(_this.is_touch == true) + document.addEventListener('mousemove', (e) => { + if(this.is_touch == true) return; - _this.selected_object_actions(e); + this.selected_object_actions(e); }); - document.addEventListener('mouseup', function (e) { - if(_this.is_touch == true) + document.addEventListener('mouseup', (e) => { + if(this.is_touch == true) return; - _this.selected_object_actions(e); + this.selected_object_actions(e); }); // touch - document.addEventListener('touchstart', function (event) { - _this.is_touch = true; - _this.selected_object_actions(event); + document.addEventListener('touchstart', (event) => { + this.is_drag = false; + this.is_touch = true; + if (!e.target.closest('#main_wrapper')) + return; + this.is_drag = true; + this.selected_object_actions(event); }); - document.addEventListener('touchmove', function (event) { - _this.selected_object_actions(event); + document.addEventListener('touchmove', (event) => { + this.selected_object_actions(event); }, {passive: false}); - document.addEventListener('touchend', function (event) { - _this.selected_object_actions(event); + document.addEventListener('touchend', (event) => { + this.selected_object_actions(event); }); } @@ -264,6 +272,9 @@ class Base_selection_class { 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) { @@ -288,7 +299,7 @@ class Base_selection_class { height: settings.data.height, }; } - if (event_type == 'mousemove' && this.mouse_lock == 'selected_object_actions') { + if (event_type == 'mousemove' && this.mouse_lock == 'selected_object_actions' && this.is_drag) { const allowNegativeDimensions = settings.data.render_function && ['line', 'gradient'].includes(settings.data.render_function[0]); diff --git a/src/js/core/base-state.js b/src/js/core/base-state.js index 692dbac..404e08d 100644 --- a/src/js/core/base-state.js +++ b/src/js/core/base-state.js @@ -61,6 +61,7 @@ class Base_state_class { await action.do(); } catch (error) { // Action aborted. This could be expected behavior if detected that the action shouldn't run. + console.info(error); return { status: 'aborted', reason: error }; } // Remove all redo actions from history @@ -106,6 +107,7 @@ class Base_state_class { save() { + /* this.optimize(); if (this.enabled == false) { @@ -155,6 +157,7 @@ class Base_state_class { } ); } + */ } /** diff --git a/src/js/core/gui/gui-colors.js b/src/js/core/gui/gui-colors.js index f5adf60..6bf5338 100644 --- a/src/js/core/gui/gui-colors.js +++ b/src/js/core/gui/gui-colors.js @@ -341,7 +341,6 @@ class GUI_colors_class { // Initialize hex entry this.inputs.hex .on('input', (event) => { - console.log(event); const value = this.inputs.hex.val(); const trimmedValue = value.trim(); if (value !== trimmedValue) { diff --git a/src/js/core/gui/gui-layers.js b/src/js/core/gui/gui-layers.js index 68312ec..8f510ae 100644 --- a/src/js/core/gui/gui-layers.js +++ b/src/js/core/gui/gui-layers.js @@ -50,13 +50,15 @@ class GUI_layers_class { } else if (target.id == 'layer_up') { //move layer up - window.State.save(); - _this.Base_layers.move(config.layer.id, 1); + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, 1) + ); } else if (target.id == 'layer_down') { //move layer down - window.State.save(); - _this.Base_layers.move(config.layer.id, -1); + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, -1) + ); } else if (target.id == 'visibility') { //change visibility @@ -80,8 +82,9 @@ class GUI_layers_class { } else if (target.id == 'delete_filter') { //delete filter - window.State.save(); - _this.Base_layers.delete_filter(target.dataset.pid, target.dataset.id); + app.State.do_action( + new app.Actions.Delete_layer_filter_action(target.dataset.pid, target.dataset.id) + ); } }); @@ -107,38 +110,40 @@ class GUI_layers_class { document.getElementById(target_id).innerHTML = ''; var html = ''; + + if (config.layer) { + for (var i in layers) { + var value = layers[i]; - for (var i in layers) { - var value = layers[i]; + if (value.id == config.layer.id) + html += '
'; + else + html += '
'; + if (value.visible == true) + html += ' '; + else + html += ' '; + html += ' '; + html += ' ' + value.name + ''; + html += '
'; + html += '
'; - if (value.id == config.layer.id) - html += '
'; - else - html += '
'; - if (value.visible == true) - html += ' '; - else - html += ' '; - html += ' '; - html += ' ' + value.name + ''; - html += '
'; - html += '
'; + //show filters + if (layers[i].filters.length > 0) { + html += '
'; + for (var j in layers[i].filters) { + var filter = layers[i].filters[j]; + var title = this.Helper.ucfirst(filter.name); + title = title.replace(/-/g, ' '); - //show filters - if (layers[i].filters.length > 0) { - html += '
'; - 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 += '
'; - html += ' '; - html += ' ' + title + ''; - html += '
'; + html += '
'; + html += ' '; + html += ' ' + title + ''; + html += '
'; + html += '
'; + } html += '
'; } - html += '
'; } } diff --git a/src/js/modules/effects/abstract/css.js b/src/js/modules/effects/abstract/css.js index 28cb655..84f149b 100644 --- a/src/js/modules/effects/abstract/css.js +++ b/src/js/modules/effects/abstract/css.js @@ -1,3 +1,4 @@ +import app from './../../../app.js'; import config from './../../../config.js'; import Dialog_class from './../../../libs/popup.js'; import Base_layers_class from './../../../core/base-layers.js'; @@ -38,7 +39,9 @@ class Effects_common_class { } save(params, type) { - this.Base_layers.add_filter(null, type, params); + return app.State.do_action( + new app.Actions.Add_layer_filter_action(null, type, params) + ); } preview(params, type) { diff --git a/src/js/modules/effects/black_and_white.js b/src/js/modules/effects/black_and_white.js index 54cb4b1..1211967 100644 --- a/src/js/modules/effects/black_and_white.js +++ b/src/js/modules/effects/black_and_white.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -66,7 +67,9 @@ class Effects_backAndWhite_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/blueprint.js b/src/js/modules/effects/blueprint.js index f548dbd..1fb9406 100644 --- a/src/js/modules/effects/blueprint.js +++ b/src/js/modules/effects/blueprint.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -32,7 +33,9 @@ class Effects_blueprint_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/box_blur.js b/src/js/modules/effects/box_blur.js index 8407f1a..736c331 100644 --- a/src/js/modules/effects/box_blur.js +++ b/src/js/modules/effects/box_blur.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -52,7 +53,9 @@ class Effects_boxBlur_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/denoise.js b/src/js/modules/effects/denoise.js index cdc7481..a6be6f2 100644 --- a/src/js/modules/effects/denoise.js +++ b/src/js/modules/effects/denoise.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -51,7 +52,9 @@ class Effects_denoise_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/dither.js b/src/js/modules/effects/dither.js index a10646a..3ba4626 100644 --- a/src/js/modules/effects/dither.js +++ b/src/js/modules/effects/dither.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -50,7 +51,9 @@ class Effects_dither_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/dot_screen.js b/src/js/modules/effects/dot_screen.js index e229dc0..f846e1f 100644 --- a/src/js/modules/effects/dot_screen.js +++ b/src/js/modules/effects/dot_screen.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -51,7 +52,9 @@ class Effects_dotScreen_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/edge.js b/src/js/modules/effects/edge.js index da99eb9..c8044f9 100644 --- a/src/js/modules/effects/edge.js +++ b/src/js/modules/effects/edge.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -29,7 +30,9 @@ class Effects_edge_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data) { diff --git a/src/js/modules/effects/emboss.js b/src/js/modules/effects/emboss.js index 5536ec4..79079b7 100644 --- a/src/js/modules/effects/emboss.js +++ b/src/js/modules/effects/emboss.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -29,7 +30,9 @@ class Effects_emboss_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data) { diff --git a/src/js/modules/effects/enrich.js b/src/js/modules/effects/enrich.js index 4d413d6..f72e623 100644 --- a/src/js/modules/effects/enrich.js +++ b/src/js/modules/effects/enrich.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -48,7 +49,9 @@ class Effects_enrich_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/grains.js b/src/js/modules/effects/grains.js index 02fab7c..3db95c7 100644 --- a/src/js/modules/effects/grains.js +++ b/src/js/modules/effects/grains.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -51,7 +52,9 @@ class Effects_grains_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/heatmap.js b/src/js/modules/effects/heatmap.js index ff37082..e08f451 100644 --- a/src/js/modules/effects/heatmap.js +++ b/src/js/modules/effects/heatmap.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -28,7 +29,9 @@ class Effects_heatmap_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data) { diff --git a/src/js/modules/effects/instagram/1977.js b/src/js/modules/effects/instagram/1977.js index cf00ad5..8a3045f 100644 --- a/src/js/modules/effects/instagram/1977.js +++ b/src/js/modules/effects/instagram/1977.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -29,7 +30,9 @@ class Effects_1977_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/aden.js b/src/js/modules/effects/instagram/aden.js index 8240831..fb2dff7 100644 --- a/src/js/modules/effects/instagram/aden.js +++ b/src/js/modules/effects/instagram/aden.js @@ -1,4 +1,4 @@ -import config from '../../../config.js'; +import app from '../../../app.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; import alertify from 'alertifyjs/build/alertify.min.js'; @@ -16,8 +16,6 @@ class Effects_aden_class { return; } - window.State.save(); - //get canvas from layer var canvas = this.Base_layers.convert_layer_to_canvas(null, true); var ctx = canvas.getContext("2d"); @@ -28,7 +26,9 @@ class Effects_aden_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/clarendon.js b/src/js/modules/effects/instagram/clarendon.js index 432eb1d..7c39bc7 100644 --- a/src/js/modules/effects/instagram/clarendon.js +++ b/src/js/modules/effects/instagram/clarendon.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -29,7 +30,9 @@ class Effects_clarendon_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/gingham.js b/src/js/modules/effects/instagram/gingham.js index 1e4ea79..219c8a1 100644 --- a/src/js/modules/effects/instagram/gingham.js +++ b/src/js/modules/effects/instagram/gingham.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -28,7 +29,9 @@ class Effects_gingham_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/inkwell.js b/src/js/modules/effects/instagram/inkwell.js index ae8f983..1f7d6cf 100644 --- a/src/js/modules/effects/instagram/inkwell.js +++ b/src/js/modules/effects/instagram/inkwell.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -33,7 +34,9 @@ class Effects_inkwell_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/lofi.js b/src/js/modules/effects/instagram/lofi.js index 681e388..816a4c5 100644 --- a/src/js/modules/effects/instagram/lofi.js +++ b/src/js/modules/effects/instagram/lofi.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -27,7 +28,9 @@ class Effects_lofi_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/toaster.js b/src/js/modules/effects/instagram/toaster.js index cfd3d1f..1a5bcd4 100644 --- a/src/js/modules/effects/instagram/toaster.js +++ b/src/js/modules/effects/instagram/toaster.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -32,7 +33,9 @@ class Effects_toaster_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/valencia.js b/src/js/modules/effects/instagram/valencia.js index 4252ec0..1adda0b 100644 --- a/src/js/modules/effects/instagram/valencia.js +++ b/src/js/modules/effects/instagram/valencia.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -28,7 +29,9 @@ class Effects_valencia_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/instagram/xpro2.js b/src/js/modules/effects/instagram/xpro2.js index 86465a9..9f0b20e 100644 --- a/src/js/modules/effects/instagram/xpro2.js +++ b/src/js/modules/effects/instagram/xpro2.js @@ -1,3 +1,4 @@ +import app from '../../../app.js'; import config from '../../../config.js'; import Dialog_class from '../../../libs/popup.js'; import Base_layers_class from '../../../core/base-layers.js'; @@ -28,7 +29,9 @@ class Effects_xpro2_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/mosaic.js b/src/js/modules/effects/mosaic.js index 542238e..491f28b 100644 --- a/src/js/modules/effects/mosaic.js +++ b/src/js/modules/effects/mosaic.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -50,7 +51,9 @@ class Effects_mosaic_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/night_vision.js b/src/js/modules/effects/night_vision.js index 1b46416..67e4d93 100644 --- a/src/js/modules/effects/night_vision.js +++ b/src/js/modules/effects/night_vision.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -32,7 +33,9 @@ class Effects_nightVision_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/oil.js b/src/js/modules/effects/oil.js index ea1a1fe..a08ec30 100644 --- a/src/js/modules/effects/oil.js +++ b/src/js/modules/effects/oil.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -51,7 +52,9 @@ class Effects_oil_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/pencil.js b/src/js/modules/effects/pencil.js index b01e6dc..a04c4e1 100644 --- a/src/js/modules/effects/pencil.js +++ b/src/js/modules/effects/pencil.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -28,7 +29,9 @@ class Effects_pencil_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, width, height) { diff --git a/src/js/modules/effects/sharpen.js b/src/js/modules/effects/sharpen.js index 928ed12..8d009c6 100644 --- a/src/js/modules/effects/sharpen.js +++ b/src/js/modules/effects/sharpen.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -50,7 +51,9 @@ class Effects_sharpen_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, params) { diff --git a/src/js/modules/effects/solarize.js b/src/js/modules/effects/solarize.js index acf77ca..d36233d 100644 --- a/src/js/modules/effects/solarize.js +++ b/src/js/modules/effects/solarize.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -29,7 +30,9 @@ class Effects_solarize_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data) { diff --git a/src/js/modules/effects/tilt_shift.js b/src/js/modules/effects/tilt_shift.js index b0dacaa..edab6d7 100644 --- a/src/js/modules/effects/tilt_shift.js +++ b/src/js/modules/effects/tilt_shift.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -73,7 +74,9 @@ class Effects_tiltShift_class { this.change(canvas, params); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/vibrance.js b/src/js/modules/effects/vibrance.js index 3f5fc35..f2d2b1d 100644 --- a/src/js/modules/effects/vibrance.js +++ b/src/js/modules/effects/vibrance.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -51,7 +52,9 @@ class Effects_vibrance_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/vignette.js b/src/js/modules/effects/vignette.js index f2fbbc1..a12e0bb 100644 --- a/src/js/modules/effects/vignette.js +++ b/src/js/modules/effects/vignette.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -52,7 +53,9 @@ class Effects_vignette_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/vintage.js b/src/js/modules/effects/vintage.js index ae5d953..b11321f 100644 --- a/src/js/modules/effects/vintage.js +++ b/src/js/modules/effects/vintage.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -49,7 +50,9 @@ class Effects_vintage_class { this.change(canvas, params); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/effects/zoom_blur.js b/src/js/modules/effects/zoom_blur.js index 6b3a308..6248bf0 100644 --- a/src/js/modules/effects/zoom_blur.js +++ b/src/js/modules/effects/zoom_blur.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Dialog_class from './../../libs/popup.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -60,7 +61,9 @@ class Effects_zoomBlur_class { ctx.drawImage(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(canvas, params) { diff --git a/src/js/modules/image/auto_adjust.js b/src/js/modules/image/auto_adjust.js index 17fedfc..84cf83b 100644 --- a/src/js/modules/image/auto_adjust.js +++ b/src/js/modules/image/auto_adjust.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -54,7 +55,9 @@ class Image_autoAdjust_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } get_adjust_data(data) { diff --git a/src/js/modules/image/color_corrections.js b/src/js/modules/image/color_corrections.js index 3873b24..50de741 100644 --- a/src/js/modules/image/color_corrections.js +++ b/src/js/modules/image/color_corrections.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -61,7 +62,9 @@ class Image_colorCorrections_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } do_corrections(data, params) { diff --git a/src/js/modules/image/decrease_colors.js b/src/js/modules/image/decrease_colors.js index ae7f90f..7707b7c 100644 --- a/src/js/modules/image/decrease_colors.js +++ b/src/js/modules/image/decrease_colors.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -53,7 +54,9 @@ class Image_decreaseColors_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } get_decreased_data(data, colors, greyscale) { diff --git a/src/js/modules/image/flip.js b/src/js/modules/image/flip.js index 9708f87..fd6178f 100644 --- a/src/js/modules/image/flip.js +++ b/src/js/modules/image/flip.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; @@ -47,7 +48,9 @@ class Image_flip_class { } //save - this.Base_layers.update_layer_image(canvas2); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas2) + ); } } diff --git a/src/js/modules/image/resize.js b/src/js/modules/image/resize.js index eb3e8cb..0b37f6c 100644 --- a/src/js/modules/image/resize.js +++ b/src/js/modules/image/resize.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Base_gui_class from './../../core/base-gui.js'; @@ -58,7 +59,7 @@ class Image_resize_class { {name: "mode", title: "Mode:", values: ["Lanczos", "Hermite", "Basic"]}, {name: "sharpen", title: "Sharpen:", value: false}, - {name: "layers", title: "Layers:", values: ["Active", "All"], value: "Active"}, + {name: "layers", title: "Layers:", values: ["All", "Active"], value: "All"}, ], on_finish: function (params) { _this.do_resize(params); @@ -67,50 +68,61 @@ class Image_resize_class { this.POP.show(settings); } - do_resize(params) { + async do_resize(params) { //validate - if (isNaN(params.width) && isNaN(params.height) && isNaN(params.width_percent) && isNaN(params.height_percent)){ + if (isNaN(params.width) && isNaN(params.height) && isNaN(params.width_percent) && isNaN(params.height_percent)) { alertify.error('Missing at least 1 size parameter.'); return false; } - if (params.width == config.WIDTH && params.height == config.HEIGHT){ + if (params.layers == 'All' && params.width == config.WIDTH && params.height == config.HEIGHT) { return false; } window.State.save(); + + // Build a list of actions to execute for resize + let actions = []; if (params.layers == 'All') { //resize all layers var skips = 0; for (var i in config.layers) { - var response = this.resize_layer(config.layers[i], params); - if(response === false){ + try { + actions = actions.concat(await this.resize_layer(config.layers[i], params)); + + } catch (error) { skips++; } } if (skips > 0) { alertify.error(skips + ' layer(s) were skipped.'); } + actions = actions.concat(this.resize_gui(params)); } else { //only active - this.resize_layer(config.layer, params); + actions = actions.concat(await this.resize_layer(config.layer, params)); } + return app.State.do_action( + new app.Actions.Bundle_action('resize_layers', 'Resize Layers', actions) + ); } /** - * it will try to resize layer (image, text, vector), returns false on failure. + * Generates actions that will resize layer (image, text, vector), returns a promise that rejects on failure. * * @param {object} layer * @param {object} params - * @returns {undefined|Boolean} + * @returns {Promise} Returns array of actions to perform */ - resize_layer(layer, params) { + async resize_layer(layer, params) { var mode = params.mode; var width = parseInt(params.width); var height = parseInt(params.height); var width_100 = parseInt(params.width_percent); var height_100 = parseInt(params.height_percent); + var canvas_width; + var canvas_height; var sharpen = params.sharpen; var _this = this; @@ -118,52 +130,71 @@ class Image_resize_class { if (isNaN(width) && isNaN(height)) { if (isNaN(width_100) == false) { width = Math.round(layer.width * width_100 / 100); + canvas_width = Math.round(config.WIDTH * width_100 / 100); } if (isNaN(height_100) == false) { height = Math.round(layer.height * height_100 / 100); + canvas_height = Math.round(config.HEIGHT * height_100 / 100); } } //if only 1 dimension was provided if (isNaN(width) || isNaN(height)) { var ratio = layer.width / layer.height; + var canvas_ratio = config.WIDTH / config.HEIGHT; if (isNaN(width)) width = Math.round(height * ratio); + canvas_width = Math.round(canvas_height * canvas_ratio); if (isNaN(height)) height = Math.round(width / ratio); + canvas_height = Math.round(canvas_width / canvas_ratio); } + + let new_x = params.layers == 'All' ? Math.round(layer.x * canvas_width / config.WIDTH) : layer.x; + let new_y = params.layers == 'All' ? Math.round(layer.y * canvas_height / config.HEIGHT) : layer.y; //is text - if(layer.type == 'text'){ + if (layer.type == 'text') { var xratio = width / layer.width; - for (let line of layer.data) { + let data = JSON.parse(JSON.stringify(layer.data)); + for (let line of data) { for (let span of line) { span.meta.size = Math.ceil((span.meta.size || textMetaDefaults.size) * xratio); span.meta.stroke_size = parseFloat((0.1 * Math.round((span.meta.stroke_size != null ? span.meta.stroke_size : textMetaDefaults.stroke_size) * xratio / 0.1)).toFixed(1)); span.meta.kerning = Math.ceil((span.meta.kerning || textMetaDefaults.kerning) * xratio); } } - layer.width = width; - layer.height = height; - this.resize_gui(); - config.need_render = true; - return true; + + // Return actions + return [ + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + data, + width, + height + }) + ]; } //is vector - if(layer.is_vector == true && layer.width != null && layer.height != null){ - layer.width = width; - layer.height = height; - this.resize_gui(); - config.need_render = true; - return true; + else if (layer.is_vector == true && layer.width != null && layer.height != null) { + // Return actions + return [ + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + width, + height + }) + ]; } //only images supported at this point - if (layer.type != 'image') { + else if (layer.type != 'image') { //error - no support alertify.error('Layer must be vector or image (convert it to raster).'); - return false; + throw new Error('Layer is not compatible with resize'); } //get canvas from layer @@ -184,19 +215,15 @@ class Image_resize_class { tmp_data.width = width; tmp_data.height = height; - this.pica.resize(canvas, tmp_data, { + await this.pica.resize(canvas, tmp_data, { alpha: true, }) - .then(function(result) { + .then((result) => { ctx.clearRect(0, 0, canvas.width, canvas.height); canvas.width = width; canvas.height = height; - ctx.drawImage(tmp_data, 0, 0, width, height); - - finish_resize(); }); - return; } else if (mode == "Hermite") { //Hermite resample @@ -216,48 +243,59 @@ class Image_resize_class { ctx.drawImage(tmp_data, 0, 0, width, height); } - finish_resize(); - - //private finish action - function finish_resize(){ - if (sharpen == true) { - var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - var filtered = _this.ImageFilters.Sharpen(imageData, 1); //add effect - ctx.putImageData(filtered, 0, 0); - } - - //save - _this.Base_layers.update_layer_image(canvas, layer.id); - layer.width = canvas.width; - layer.height = canvas.height; - layer.width_original = canvas.width; - layer.height_original = canvas.height; - config.need_render = true; - - _this.resize_gui(); + if (sharpen == true) { + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + var filtered = _this.ImageFilters.Sharpen(imageData, 1); //add effect + ctx.putImageData(filtered, 0, 0); } + + // Return actions + return [ + new app.Actions.Update_layer_image_action(canvas, layer.id), + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + width: canvas.width, + height: canvas.height, + width_original: canvas.width, + height_original: canvas.height + }) + ]; } - resize_gui() { - var max_x = 0; - var max_y = 0; - - for (var i = 0; i < config.layers.length; i++) { - var layer = config.layers[i]; - - if(layer.width == null || layer.height == null || layer.x == null || layer.y == null){ - //layer without dimensions - continue; - } + resize_gui(params) { + var width = parseInt(params.width); + var height = parseInt(params.height); + var width_100 = parseInt(params.width_percent); + var height_100 = parseInt(params.height_percent); - max_x = Math.max(max_x, layer.x + layer.width); - max_y = Math.max(max_y, layer.y + layer.height); + //if dimension with percent provided + if (isNaN(width) && isNaN(height)) { + if (isNaN(width_100) == false) { + width = Math.round(config.WIDTH * width_100 / 100); + } + if (isNaN(height_100) == false) { + height = Math.round(config.HEIGHT * height_100 / 100); + } } - - config.WIDTH = parseInt(max_x); - config.HEIGHT = parseInt(max_y); - this.Base_gui.prepare_canvas(); - config.need_render = true; + + //if only 1 dimension was provided + if (isNaN(width) || isNaN(height)) { + var ratio = config.WIDTH / config.HEIGHT; + if (isNaN(width)) + width = Math.round(height * ratio); + if (isNaN(height)) + height = Math.round(width / ratio); + } + + return [ + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: parseInt(width), + HEIGHT: parseInt(height) + }), + new app.Actions.Prepare_canvas_action('do') + ]; } } diff --git a/src/js/modules/image/trim.js b/src/js/modules/image/trim.js index 32ca9f9..04c5e6b 100644 --- a/src/js/modules/image/trim.js +++ b/src/js/modules/image/trim.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_gui_class from './../../core/base-gui.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -38,9 +39,7 @@ class Image_trim_class { }, false); } - trim(){ - var _this = this; - + trim() { var removeWhiteColor = false; if(config.TRANSPARENCY == false) removeWhiteColor = true; @@ -53,58 +52,64 @@ class Image_trim_class { {}, //gap {name: "remove_white", title: "Trim white color?", value: removeWhiteColor}, ], - on_finish: function (params) { - window.State.save(); - if(params.trim_layer == true) - _this.trim_layer(config.layer.id, params.remove_white); - if(params.trim_all == true) - _this.trim_all(params.remove_white); + on_finish: (params) => { + let actions = []; + if (params.trim_layer == true) + actions = actions.concat(this.trim_layer(config.layer.id, params.remove_white)); + if (params.trim_all == true) + actions = actions.concat(this.trim_all(params.remove_white)); + if (actions.length > 0) { + app.State.do_action( + new app.Actions.Bundle_action('trim_layers', 'Trim Layers', actions) + ); + } }, }; this.Dialog.show(settings); } - trim_layer(layer_id, removeWhiteColor = false){ + trim_layer(layer_id, removeWhiteColor = false) { var layer = this.Base_layers.get_layer(layer_id); - if (config.layer.type != 'image') { + if (layer.type != 'image') { alertify.error('Skip - layer must be image.'); return false; } var trim = this.get_trim_info(layer_id, removeWhiteColor); trim = trim.relative; - - if(layer.type == 'image'){ - //if image was stretched - var width_ratio = (layer.width / layer.width_original); - var height_ratio = (layer.height / layer.height_original); + + //if image was stretched + var width_ratio = (layer.width / layer.width_original); + var height_ratio = (layer.height / layer.height_original); - //create smaller canvas - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext("2d"); - canvas.width = trim.width / width_ratio; - canvas.height = trim.height / height_ratio; + //create smaller canvas + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = trim.width / width_ratio; + canvas.height = trim.height / height_ratio; - //cut required part - ctx.translate(-trim.left / width_ratio, -trim.top / height_ratio); - canvas.getContext("2d").drawImage(layer.link, 0, 0); - ctx.translate(0, 0); - this.Base_layers.update_layer_image(canvas, layer.id); + //cut required part + ctx.translate(-trim.left / width_ratio, -trim.top / height_ratio); + canvas.getContext("2d").drawImage(layer.link, 0, 0); + ctx.translate(0, 0); - //update attributes - layer.width = Math.ceil(canvas.width * width_ratio); - layer.height = Math.ceil(canvas.height * height_ratio); - layer.x += trim.left; - layer.y += trim.top; - layer.width_original = canvas.width; - layer.height_original = canvas.height; - } - - config.need_render = true; + return [ + new app.Actions.Update_layer_image_action(canvas, layer.id), + new app.Actions.Update_layer_action(layer.id, { + x: layer.x + trim.left, + y: layer.y + trim.top, + width: Math.ceil(canvas.width * width_ratio), + height: Math.ceil(canvas.height * height_ratio), + width_original: canvas.width, + height_original: canvas.height + }) + ]; } trim_all(removeWhiteColor = false) { + let actions = []; + var all_top = config.HEIGHT; var all_left = config.WIDTH; var all_bottom = config.HEIGHT; @@ -118,12 +123,12 @@ class Image_trim_class { } //collect info - for (var i = 0; i < config.layers.length; i++) { - var layer = config.layers[i]; + for (let i = 0; i < config.layers.length; i++) { + let layer = config.layers[i]; - if(layer.width == null || layer.height == null || layer.x == null || layer.y == null){ + if (layer.width == null || layer.height == null || layer.x == null || layer.y == null) { //layer without dimensions - var trim_info = this.get_trim_info(layer.id, removeWhiteColor); + const trim_info = this.get_trim_info(layer.id, removeWhiteColor); all_top = Math.min(all_top, trim_info.top); all_left = Math.min(all_left, trim_info.left); @@ -139,25 +144,29 @@ class Image_trim_class { } //move every layer - for (var i = 0; i < config.layers.length; i++) { - var layer = config.layers[i]; + for (let i = 0; i < config.layers.length; i++) { + let layer = config.layers[i]; if (layer.x == null || layer.y == null || layer.type == null) continue; - layer.x = layer.x - all_left; - layer.y = layer.y - all_top; + actions.push( + new app.Actions.Update_layer_action(layer.id, { + x: layer.x - all_left, + y: layer.y - all_top + }) + ); } //resize - config.WIDTH = config.WIDTH - all_left - all_right; - config.HEIGHT = config.HEIGHT - all_top - all_bottom; - if (config.WIDTH < 1) - config.WIDTH = 1; - if (config.HEIGHT < 1) - config.HEIGHT = 1; - - this.Base_gui.prepare_canvas(); - config.need_render = true; + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: Math.max(1, config.WIDTH - all_left - all_right), + HEIGHT: Math.max(1, config.HEIGHT - all_top - all_bottom) + }), + new app.Actions.Prepare_canvas_action('do') + ); + return actions; } /** diff --git a/src/js/modules/layer/clear.js b/src/js/modules/layer/clear.js index 9f5ae3e..dbc758c 100644 --- a/src/js/modules/layer/clear.js +++ b/src/js/modules/layer/clear.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -8,9 +9,9 @@ class Layer_clear_class { } clear() { - window.State.save(); - - this.Base_layers.layer_clear(config.layer.id); + return app.State.do_action( + new app.Actions.Clear_layer_action(config.layer.id) + ); } } diff --git a/src/js/modules/layer/flatten.js b/src/js/modules/layer/flatten.js index 3bced7f..cd898d7 100644 --- a/src/js/modules/layer/flatten.js +++ b/src/js/modules/layer/flatten.js @@ -46,7 +46,6 @@ class Layer_flatten_class { for (var i = config.layers.length - 1; i >= 0; i--) { delete_actions.push(new app.Actions.Delete_layer_action(config.layers[i].id)); } - console.log(delete_actions); // Run actions app.State.do_action( new app.Actions.Bundle_action('flatten_image', 'Flatten Image', [ diff --git a/src/js/modules/layer/move.js b/src/js/modules/layer/move.js index cdc2d9a..f27e3a0 100644 --- a/src/js/modules/layer/move.js +++ b/src/js/modules/layer/move.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; @@ -8,13 +9,15 @@ class Layer_move_class { } up() { - window.State.save(); - this.Base_layers.move(config.layer.id, 1); + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, 1) + ); } down() { - window.State.save(); - this.Base_layers.move(config.layer.id, -1); + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, -1) + ); } } diff --git a/src/js/modules/tools/color_to_alpha.js b/src/js/modules/tools/color_to_alpha.js index c80f12c..5cc018c 100644 --- a/src/js/modules/tools/color_to_alpha.js +++ b/src/js/modules/tools/color_to_alpha.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -50,7 +51,9 @@ class Tools_colorToAlpha_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, color) { diff --git a/src/js/modules/tools/color_zoom.js b/src/js/modules/tools/color_zoom.js index cfc52ff..78d80f7 100644 --- a/src/js/modules/tools/color_zoom.js +++ b/src/js/modules/tools/color_zoom.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -49,7 +50,9 @@ class Tools_colorZoom_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } change(data, zoom, center) { diff --git a/src/js/modules/tools/content_fill.js b/src/js/modules/tools/content_fill.js index e886d56..03cef50 100644 --- a/src/js/modules/tools/content_fill.js +++ b/src/js/modules/tools/content_fill.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -63,11 +64,17 @@ class Tools_contentFill_class { this.change(canvas, params); //save - config.layer.x = 0; - config.layer.y = 0; - config.layer.width = config.WIDTH; - config.layer.height = config.HEIGHT; - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Bundle_action('content_fill', 'Content Fill', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: 0, + y: 0, + width: config.WIDTH, + height: config.HEIGHT + }), + new app.Actions.Update_layer_image_data(canvas) + ]) + ); } change(canvas, params) { diff --git a/src/js/modules/tools/replace_color.js b/src/js/modules/tools/replace_color.js index 69594e8..1868f24 100644 --- a/src/js/modules/tools/replace_color.js +++ b/src/js/modules/tools/replace_color.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -54,7 +55,9 @@ class Tools_replaceColor_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } do_replace(data, params) { diff --git a/src/js/modules/tools/restore_alpha.js b/src/js/modules/tools/restore_alpha.js index 19e327d..a69aaa0 100644 --- a/src/js/modules/tools/restore_alpha.js +++ b/src/js/modules/tools/restore_alpha.js @@ -1,3 +1,4 @@ +import app from './../../app.js'; import config from './../../config.js'; import Base_layers_class from './../../core/base-layers.js'; import Dialog_class from './../../libs/popup.js'; @@ -48,7 +49,9 @@ class Tools_restoreAlpha_class { ctx.putImageData(data, 0, 0); //save - this.Base_layers.update_layer_image(canvas); + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); } recover_alpha(data, level) { diff --git a/src/js/tools/blur.js b/src/js/tools/blur.js index 138d17e..e357249 100644 --- a/src/js/tools/blur.js +++ b/src/js/tools/blur.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -128,7 +129,11 @@ class Blur_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('blur_tool', 'Blur Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/bulge_pinch.js b/src/js/tools/bulge_pinch.js index 3e29719..8bc3b21 100644 --- a/src/js/tools/bulge_pinch.js +++ b/src/js/tools/bulge_pinch.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -105,7 +106,11 @@ class BulgePinch_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('bulge_pinch_tool', 'Bulge/Pinch Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/clone.js b/src/js/tools/clone.js index a497e6e..8c88ec2 100644 --- a/src/js/tools/clone.js +++ b/src/js/tools/clone.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -259,7 +260,11 @@ class Clone_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('clone_tool', 'Clone Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/crop.js b/src/js/tools/crop.js index 4e4183e..72474e7 100644 --- a/src/js/tools/crop.js +++ b/src/js/tools/crop.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -30,28 +31,30 @@ class Crop_class extends Base_tools_class { return _this.selection; }, }; + this.mousedown_selection = null; this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); } dragStart(event) { - var _this = this; - if (config.TOOL.name != _this.name) + this.is_mousedown_canvas = false; + if (config.TOOL.name != this.name) return; - _this.mousedown(event); + if (!event.target.closest('#main_wrapper')) + return; + this.is_mousedown_canvas = true; + this.mousedown(event); } dragMove(event) { - var _this = this; - if (config.TOOL.name != _this.name) + if (config.TOOL.name != this.name) return; - _this.mousemove(event); + this.mousemove(event); } dragEnd(event) { - var _this = this; - if (config.TOOL.name != _this.name) + if (config.TOOL.name != this.name) return; - _this.mouseup(event); + this.mouseup(event); } load() { @@ -82,9 +85,11 @@ class Crop_class extends Base_tools_class { mousedown(e) { var mouse = this.get_mouse_info(e); - if (mouse.valid == false || mouse.click_valid == false) + if (this.Base_selection.is_drag == false || mouse.valid == false || mouse.click_valid == false) return; + this.mousedown_selection = JSON.parse(JSON.stringify(this.selection)); + if (this.Base_selection.mouse_lock !== null) { return; } @@ -95,7 +100,7 @@ class Crop_class extends Base_tools_class { mousemove(e) { var mouse = this.get_mouse_info(e); - if (mouse.is_drag == false) { + if (this.Base_selection.is_drag == false || mouse.is_drag == false) { return; } if (e.type == 'mousedown' && (mouse.valid == false || mouse.click_valid == false)) { @@ -108,7 +113,7 @@ class Crop_class extends Base_tools_class { var width = mouse.x - mouse.click_x; var height = mouse.y - mouse.click_y; - if(event.ctrlKey == true || event.metaKey){ + if(e.ctrlKey == true || e.metaKey){ //ctrl is pressed - crop will be calculated based on global width and height ratio var ratio = config.WIDTH / config.HEIGHT; var width_new = Math.round(height * ratio); @@ -134,6 +139,9 @@ class Crop_class extends Base_tools_class { mouseup(e) { var mouse = this.get_mouse_info(e); + if (!this.Base_selection.is_drag) { + return; + } if (e.type == 'mousedown' && mouse.click_valid == false) { return; } @@ -183,7 +191,9 @@ class Crop_class extends Base_tools_class { this.selection.height = config.HEIGHT - this.selection.y; } - config.need_render = true; + app.State.do_action( + new app.Actions.Set_selection_action(this.selection.x, this.selection.y, this.selection.width, this.selection.height, this.mousedown_selection) + ); } render(ctx, layer) { @@ -193,7 +203,7 @@ class Crop_class extends Base_tools_class { /** * do actual crop */ - on_params_update() { + async on_params_update() { var params = this.getParams(); var selection = this.selection; params.crop = true; @@ -229,69 +239,98 @@ class Crop_class extends Base_tools_class { selection.width = Math.min(selection.width, config.WIDTH); selection.height = Math.min(selection.height, config.HEIGHT); + let actions = []; + for (var i in config.layers) { var link = config.layers[i]; if (link.type == null) continue; + let x = link.x; + let y = link.y; + let width = link.width; + let height = link.height; + let width_original = link.width_original; + let height_original = link.height_original; + //move - link.x -= parseInt(selection.x); - link.y -= parseInt(selection.y); + x -= parseInt(selection.x); + y -= parseInt(selection.y); if (link.type == 'image') { //also remove unvisible data - var left = 0; - if (link.x < 0) - left = -link.x; - var top = 0; - if (link.y < 0) - top = -link.y; - var right = 0; - if (link.x + link.width > selection.width) - right = link.x + link.width - selection.width; - var bottom = 0; - if (link.y + link.height > selection.height) - bottom = link.y + link.height - selection.height; - var width = link.width - left - right; - var height = link.height - top - bottom; + let left = 0; + if (x < 0) + left = -x; + let top = 0; + if (y < 0) + top = -y; + let right = 0; + if (x + width > selection.width) + right = x + width - selection.width; + let bottom = 0; + if (y + height > selection.height) + bottom = y + height - selection.height; + let crop_width = width - left - right; + let crop_height = height - top - bottom; //if image was streched - var width_ratio = (link.width / link.width_original); - var height_ratio = (link.height / link.height_original); + let width_ratio = (width / width_original); + let height_ratio = (height / height_original); //create smaller canvas - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext("2d"); - canvas.width = width / width_ratio; - canvas.height = height / height_ratio; + let canvas = document.createElement('canvas'); + let ctx = canvas.getContext("2d"); + canvas.width = crop_width / width_ratio; + canvas.height = crop_height / height_ratio; //cut required part ctx.translate(-left / width_ratio, -top / height_ratio); canvas.getContext("2d").drawImage(link.link, 0, 0); ctx.translate(0, 0); - this.Base_layers.update_layer_image(canvas, link.id); + actions.push( + new app.Actions.Update_layer_image_action(canvas, link.id) + ); //update attributes - link.width = Math.ceil(canvas.width * width_ratio); - link.height = Math.ceil(canvas.height * height_ratio); - link.x += left; - link.y += top; - link.width_original = canvas.width; - link.height_original = canvas.height; + width = Math.ceil(canvas.width * width_ratio); + height = Math.ceil(canvas.height * height_ratio); + x += left; + y += top; + width_original = canvas.width; + height_original = canvas.height; } + + actions.push( + new app.Actions.Update_layer_action(link.id, { + x, + y, + width, + height, + width_original, + height_original + }) + ); } - config.WIDTH = parseInt(selection.width); - config.HEIGHT = parseInt(selection.height); - - this.Base_gui.prepare_canvas(); + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: parseInt(selection.width), + HEIGHT: parseInt(selection.height) + }), + new app.Actions.Prepare_canvas_action('do'), + new app.Actions.Reset_selection_action() + ); + await app.State.do_action( + new app.Actions.Bundle_action('crop_tool', 'Crop Tool', actions) + ); this.selection = { x: null, y: null, width: null, height: null, }; - this.Base_selection.reset_selection(); } on_leave() { diff --git a/src/js/tools/desaturate.js b/src/js/tools/desaturate.js index 7aa0a11..19b3480 100644 --- a/src/js/tools/desaturate.js +++ b/src/js/tools/desaturate.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -129,7 +130,11 @@ class Desaturate_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('desaturate_tool', 'Desaturate Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/erase.js b/src/js/tools/erase.js index e260cd2..2055233 100644 --- a/src/js/tools/erase.js +++ b/src/js/tools/erase.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -155,7 +156,11 @@ class Erase_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('erase_tool', 'Erase Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/fill.js b/src/js/tools/fill.js index e8b1caf..4f7bdda 100644 --- a/src/js/tools/fill.js +++ b/src/js/tools/fill.js @@ -106,7 +106,11 @@ class Fill_class extends Base_tools_class { if (config.layer.type != null) { //update - this.Base_layers.update_layer_image(canvas); + app.State.do_action( + new app.Actions.Bundle_action('fill_tool', 'Fill Tool', [ + new app.Actions.Update_layer_image_action(canvas) + ]) + ); } else { //create new @@ -119,7 +123,7 @@ class Fill_class extends Base_tools_class { params.width = canvas.width; params.height = canvas.height; app.State.do_action( - new app.Actions.Bundle_action('fill', 'Fill', [ + new app.Actions.Bundle_action('fill_tool', 'Fill Tool', [ new app.Actions.Insert_layer_action(params) ]) ); diff --git a/src/js/tools/magic_erase.js b/src/js/tools/magic_erase.js index 944b58a..fc58286 100644 --- a/src/js/tools/magic_erase.js +++ b/src/js/tools/magic_erase.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -88,7 +89,11 @@ class Magic_erase_class extends Base_tools_class { this.magic_erase_general(ctx, config.WIDTH, config.HEIGHT, mouse_x, mouse_y, params.power, params.anti_aliasing, params.contiguous); - this.Base_layers.update_layer_image(canvas); + app.State.do_action( + new app.Actions.Bundle_action('magic_erase_tool', 'Magic Eraser Tool', [ + new app.Actions.Update_layer_image_action(canvas) + ]) + ); //prevent crash bug on touch screen - hard to explain and debug await new Promise(r => setTimeout(r, 10)); this.working = false; diff --git a/src/js/tools/selection.js b/src/js/tools/selection.js index 59c18a7..4caa07a 100644 --- a/src/js/tools/selection.js +++ b/src/js/tools/selection.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -249,7 +250,11 @@ class Selection_class extends Base_tools_class { return; delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('selection_tool', 'Selection Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); this.reset_tmp_canvas(); config.need_render = true; @@ -283,7 +288,12 @@ class Selection_class extends Base_tools_class { //do erase this.tmpCanvasCtx.clearRect(mouse_x, mouse_y, selection.width, selection.height); - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('selection_tool', 'Selection Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + this.selection = { x: null, y: null, diff --git a/src/js/tools/sharpen.js b/src/js/tools/sharpen.js index 6c18ae5..008e031 100644 --- a/src/js/tools/sharpen.js +++ b/src/js/tools/sharpen.js @@ -1,3 +1,4 @@ +import app from './../app.js'; import config from './../config.js'; import Base_tools_class from './../core/base-tools.js'; import Base_layers_class from './../core/base-layers.js'; @@ -128,7 +129,11 @@ class Sharpen_class extends Base_tools_class { } delete config.layer.link_canvas; - this.Base_layers.update_layer_image(this.tmpCanvas); + app.State.do_action( + new app.Actions.Bundle_action('sharpen_tool', 'Sharpen Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); //decrease memory this.tmpCanvas.width = 1; diff --git a/src/js/tools/text.js b/src/js/tools/text.js index 6b50469..938be81 100644 --- a/src/js/tools/text.js +++ b/src/js/tools/text.js @@ -367,12 +367,6 @@ class Text_document_class { for (let i = 0; i < insertLine.length; i++) { const span = insertLine[i]; const spanLength = span.text.length; - if (span === insertedSpan) { - console.log( - (character > characterCount || character === 0), - character <= characterCount + spanLength - ); - } if (!modifyingSpan && (character > characterCount || character === 0) && character <= characterCount + spanLength) { if (insertLine[i + 1] && insertLine[i + 1].text === '') { modifyingSpan = insertLine[i + 1]; @@ -2391,6 +2385,10 @@ class Text_class extends Base_tools_class { editor.layer = layer; layerEditors.set(layer, editor); } + if (layer._needs_update_data) { + delete layer._needs_update_data; + editor.set_lines(layer.data); + } return editor; }